2

I'm new to iOS programming.

Recently, I'm trying to make a UIPickerView as an inputView in UITextfield.

The data in UIPickerView is about all the iOS built-in fonts. So I want to make two components in UIPickerView: the first is familyType, and the second is all the fonts in that familyType.

I simulate the code from this answer, but I meet some problem I can't solve. Any help is welcome!

My question is here:
Why rowOneSelected in this function always get 0 first, even I use selectedRowInComponent in advance?

// The number of rows of data
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
    if(component == 0)
    {
        return _fontTypeArray.count;
    }
    else
    {
        NSInteger rowOneSelected = [_pickerFont selectedRowInComponent:0];
        FontType *temp = _fontTypeArray[rowOneSelected];
        NSLog(@"%ld", (long)rowOneSelected);    // I use this to debug, and there is a main question: why every time it logs 0 first?
        return temp.font.count;
    }
}





All my relative code is here:

In ViewController.h:

#import <UIKit/UIKit.h>
#import "MenuLayerTwoPlusThree.h"

@interface ViewController : UIViewController

@property MenuLayerTwoPlusThree *layerTwoPlusThree;

- (void)createLayerTwoPlusThree;

@end

In ViewController.m:

- (void)viewDidLoad {
    [super viewDidLoad];

    [self createLayerTwoPlusThree];
}

- (void)createLayerTwoPlusThree
{
    _layerTwoPlusThree = [MenuLayerTwoPlusThree alloc];

    [_layerTwoPlusThree createFontArray];
    [_layerTwoPlusThree createSelectPanel];
}

In FontType.h:

#ifndef FontType_h
#define FontType_h

#import <Foundation/Foundation.h>

@interface FontType : NSObject

@property NSString *familyName;
@property NSMutableArray *font;

@end

#endif /* FontType_h */

In FontType.m:

#import <Foundation/Foundation.h>
#import "FontType.h"

@implementation FontType

@synthesize familyName;
@synthesize font;

@end

In MenuLayerTwoPlusThree.h:

#ifndef MenuLayerTwoPlusThree_h
#define MenuLayerTwoPlusThree_h

#import <UIKit/UIKit.h>
#import "FontType.h"

@interface MenuLayerTwoPlusThree : NSObject<UITextFieldDelegate, UIPickerViewDataSource, UIPickerViewDelegate>

@property UITextField *textFieldFont;    
@property NSMutableArray *fontTypeArray;
@property UIPickerView *pickerFont;
@property UIBarButtonItem *doneButton;
@property UIBarButtonItem *spaceButton;
@property UIBarButtonItem *cancelButton;
@property UIToolbar *toolBar;
@property NSArray *toolBarItems;
@property NSInteger familyType;
@property NSInteger fontType;
@property NSString *fontName;
- (void)createFontArray;
- (IBAction)pickerViewButtonClicked:(id)sender;

@end

In MenuLayerTwoPlusThree.m

- (void)createFontArray
{
    _fontTypeArray = [[NSMutableArray alloc]initWithCapacity:80];

    int number = 0;

    for(NSString* family in [UIFont familyNames])
    {
        //NSLog(@"%@", family);
        //number++;
        FontType *temp = [[FontType alloc]init];
        temp.familyName = family;
        temp.font = [[NSMutableArray alloc]init];
        int flag = 0;

        for(NSString* name in [UIFont fontNamesForFamilyName:family])
        {
            //NSLog(@" %@", name);
            //number++;
            flag++;
            [temp.font addObject:name];
        }

        // add Heiti SC, Heiti TC, Telugu Sangam MN, and Bangla Sangam MN to font array
        if(flag == 0)
        {
            [temp.font addObject:family];
        }

        [_fontTypeArray addObject:temp];
    }


    // print all fonts test
    for(FontType *x in _fontTypeArray)
    {
        number++;
        NSLog(@"%@", x.familyName);
        for(NSString *y in x.font)
        {
            //number++;
            NSLog(@"\t%@", y);
        }
    }      

    NSLog(@"//////////////////////////////");
    NSLog(@"%d", number);
}

- (void)createSelectPanel
{
    [self createSelectPanelForPancel1];
    [self createSelectPanelForPancel2];
    [self createSelectPanelForFont];
    [self createSelectPanelForShape];
    [self createSelectPanelForEraser];
}

- (void)createSelectPanelForFont
{
    _textFieldFont = [[UITextField alloc]initWithFrame:CGRectMake(19, 148, 150, 12)];
    [_textFieldFont setBackground:[UIImage imageNamed:@"font-type-bar.png"]];
    _textFieldFont.rightViewMode = UITextFieldViewModeAlways;
    _textFieldFont.delegate = self;
    [_textFieldFont setPlaceholder:@"Heiti TC"];
    _textFieldFont.font = [_textFieldFont.font fontWithSize:10 * _aspectRatio];


    // resize right view image
    UIImageView *rightViewImage = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 12 * _aspectRatio, 12 * _aspectRatio)];
    [rightViewImage setImage:[UIImage imageNamed:@"font-type-bar-roll.png"]];
    _textFieldFont.rightView = rightViewImage;


    _pickerFont = [[UIPickerView alloc]init];
    _pickerFont.dataSource = self;
    _pickerFont.delegate = self;
    _pickerFont.showsSelectionIndicator = YES;


    _doneButton = [[UIBarButtonItem alloc]initWithTitle:@"Done" style:UIBarButtonItemStyleDone target:self action:@selector(pickerViewButtonClicked:)];
    _spaceButton = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
    _cancelButton = [[UIBarButtonItem alloc]initWithTitle:@"Cancel" style:UIBarButtonItemStylePlain target:self action:@selector(pickerViewButtonClicked:)];
    _toolBar = [[UIToolbar alloc]initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, 45)];
    [_toolBar setBarStyle:UIBarStyleDefault];
    _toolBarItems = [NSArray arrayWithObjects:_cancelButton, _spaceButton, _doneButton, nil];
    [_toolBar setItems:_toolBarItems];

    _textFieldFont.inputView = _pickerFont;
    _textFieldFont.inputAccessoryView = _toolBar;
    if (@available(iOS 9.0, *)) {
        _textFieldFont.inputAssistantItem.leadingBarButtonGroups = @[];
    } else {
        // Fallback on earlier versions
    }
    if (@available(iOS 9.0, *)) {
        _textFieldFont.inputAssistantItem.trailingBarButtonGroups = @[];
    } else {
        // Fallback on earlier versions
    }

    // I want to add these codes to select row in advanced to make sure first time NSLog will print 9, but it doesn't work.
//    [_pickerFont reloadAllComponents];
//    _familyType = 9;
//    _fontType = 0;
//    _fontName = @"Heiti TC";
//    [_pickerFont selectRow:_familyType inComponent:0 animated:YES];

    [_selectPanelFontView addSubview:_textFieldFont];
}

And this is the delegate, which I am written in the MenuLayerTwoPlusThree.m:

// The number of columns of data
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
    return 2;
}

// The number of rows of data
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
    if(component == 0)
    {
        return _fontTypeArray.count;
    }
    else
    {
        NSInteger rowOneSelected = [_pickerFont selectedRowInComponent:0];
        FontType *temp = _fontTypeArray[rowOneSelected];
        NSLog(@"%ld", (long)rowOneSelected);    // I use this to debug, and there is a main question: why every time it logs 0 first?
        return temp.font.count;
    }
}

// The data to return for the row and component (column) that's being passed in
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
    if(component == 0)
    {
        FontType *temp = _fontTypeArray[row];
        return temp.familyName;
    }
    else
    {
        NSInteger rowOneSelected = [_pickerFont selectedRowInComponent:0];
        FontType *temp = _fontTypeArray[rowOneSelected];
        return [temp.font objectAtIndex:row];
    }
}

- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
    if(component == 0)
    {
        [_pickerFont reloadComponent:1];
    }

    // This block is moved to pickerViewButtonClicked (sender == _doneButton)
//    else
//    {
//        NSInteger rowOneSelected = [_pickerFont selectedRowInComponent:0];
//        FontType *temp = _fontTypeArray[rowOneSelected];
//        [_textFieldFont setText:temp.font[row]];
//    }
}

- (IBAction)pickerViewButtonClicked:(id)sender
{
    if(sender == _doneButton)
    {
        // Save value when I clicked the done button.
        _familyType = [_pickerFont selectedRowInComponent:0];
        _fontType = [_pickerFont selectedRowInComponent:1];

//        NSLog(@"family: %ld", _familyType);
//        NSLog(@"font: %ld", _fontType);

        FontType *temp = _fontTypeArray[_familyType];
        _fontName = temp.font[_fontType];
        [_textFieldFont setText:_fontName];

//        NSLog(@"font name: %@", _fontName);


        [_textFieldFont endEditing:YES];
    }
    else if(sender == _cancelButton)
    {
        [_textFieldFont endEditing:YES];

        // I want to turn back to the last selected value when I clicked the cancel button.
        [_pickerFont reloadAllComponents];
        [_pickerFont selectRow:_familyType inComponent:0 animated:NO];
        [_pickerFont selectRow:_fontType inComponent:1 animated:NO];
    }
}
Leo Liu
  • 23
  • 5
  • I'm not sure I understand your problem. What do you expect instead of the 0? – Jörn Eyrich Feb 22 '18 at 13:23
  • @JörnEyrich For example, when picker view pop up first time, I selected "Heiti TC" in column 1 row 9. And there is only one font called "Heiti TC" in column 2 row 0, so I selected them and clicked Done button to dismiss them. Next time I click the text field to let picker view pop up, I expect it will show "Heiti TC" in column 1 and "Heiti TC" in column 2. However, it will crash and show the error log "index 1 beyond bounds [0 .. 0]". – Leo Liu Feb 23 '18 at 02:09
  • I know why it shows this error log. I use NSLog to print rowOneSelected, and it will print 0 not 9. Because my data in column 1 row 0 is "Copperplate", and it has three fonts in column 2, so temp.font.count will be 3 in my code. However, there is only one row in column 2 when "Heiti TC" is selected in column 1. So it will crash immediately and shows that it is out of bound. – Leo Liu Feb 23 '18 at 02:24
  • Sorry, column 1 means component 0, and column 2 means component 1. – Leo Liu Feb 23 '18 at 02:28
  • And I try to add some codes to select the value in component 0 in advance (please look at the last few lines in MenuLayerTwoPlusThree.m), but it still crash and print 0 again. – Leo Liu Feb 23 '18 at 02:33
  • I don't know what I said is clear or not, but I am trying to make my code more understandable. If you don't get it, please let me know, I will update my code. – Leo Liu Feb 23 '18 at 02:41
  • Thanks. I think I understand now and was able to reproduce it in a small Playground experiment. Strange indeed. I'll have to play with it a bit and will report back. – Jörn Eyrich Feb 26 '18 at 13:36

2 Answers2

2

It really seems that when the UIPickerView appears as an inputView after you have selected row 9 in component 0, the selectedRowInComponent: 0 returns 0 in numberOfRowsInComponent: 1, but then returns 9 in titleForRow: row forComponent: 1.

I don't think you are doing anything wrong, so it looks like a bug in UIKit.

As a workaround, I suggest you don't ask the picker view for the selected row, but track the selected row yourself (initialize your own variable in your view controller and update it in didSelectRow: row inComponent: 0)

Jörn Eyrich
  • 5,141
  • 1
  • 19
  • 14
  • Thank you! I keep track the value of UIPickerView and find an informal way to solve this problem. I'll share my code later when the classes are all over today! – Leo Liu Mar 01 '18 at 03:05
0

Sorry, I forgot to post my solution.

I use _fontTypeIsFirst to see if UIPickerView has been selected or not. When you roll the picker view, you should set _fontTypeIsFirst to NO and reload component 1, so it can return to row 0 in component 1 as usual.

Here is my code:


Some delegates:

// The number of columns of data
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
    return 2;
}

// The number of rows of data
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
    if(component == 0)
    {
        return _fontTypeArray.count;
    }
    else
    {
        NSInteger rowOneSelected;
        if (_fontTypeIsFirst == YES)
        {

            rowOneSelected = _selectedRow;
        }
        else
        {
            rowOneSelected = [_pickerFont selectedRowInComponent:0];
        }
        FontType *temp = _fontTypeArray[rowOneSelected];
        return temp.font.count;
    }
}

// The data to return for the row and component (column) that's being passed in
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
    if(component == 0)
    {
        FontType *temp = _fontTypeArray[row];
        return temp.familyName;
    }
    else
    {
        NSInteger rowOneSelected;
        if (_fontTypeIsFirst == YES)
        {
            rowOneSelected = _selectedRow;
        }
        else
        {
            rowOneSelected = [_pickerFont selectedRowInComponent:0];
        }

        FontType *temp = _fontTypeArray[rowOneSelected];
        return [temp.font objectAtIndex:row];
    }
}

- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
    _fontTypeIsFirst = NO;

    if(component == 0)
    {
        [_pickerFont reloadComponent:1];
    }
}


And this is action of done and cancel button:

- (IBAction)pickerViewButtonClicked:(id)sender
{
    if(sender == _doneButton)
    {
        _selectedRow = [_pickerFont selectedRowInComponent:0];
        _familyType = [_pickerFont selectedRowInComponent:0];
        _fontType = [_pickerFont selectedRowInComponent:1];

        FontType *temp = _fontTypeArray[_familyType];
        _fontName = temp.font[_fontType];
        [_textFieldFont setText:_fontName];

        [_textFieldFont endEditing:YES];
        _fontTypeIsFirst = YES;
    }
    else if(sender == _cancelButton)
    {
        [_textFieldFont endEditing:YES];
        _fontTypeIsFirst = YES;

        [_pickerFont selectRow:_familyType inComponent:0 animated:NO];
        [_pickerFont selectRow:_fontType inComponent:1 animated:NO];
    }
}


And you can add codes like this to select default value of picker view in viewDidLoad or some place:

_fontTypeIsFirst = YES;
_selectedRow = 9;
_familyType = 9;
_fontType = 0;
_fontName = @"Heiti TC";
[_pickerFont selectRow:_familyType inComponent:0 animated:NO];
[_pickerFont selectRow:_fontType inComponent:1 animated:NO];
Leo Liu
  • 23
  • 5