Understanding how UIPickerView works - objective-c

fairly new, and I would need a hand understanding UIPickerViews.
I have created a UIPickerView programmatically for my project:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIPickerView *myPickerView = [[UIPickerView alloc] initWithFrame:CGRectMake(0, 200, 375, 200)];
myPickerView.delegate = self;
myPickerView.showsSelectionIndicator = YES;
[self.view addSubview:myPickerView];
}
And then added a method for the number of rows:
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
NSUInteger numRows = 5;
return numRows;
}
Which returns as expected five question marks. I could then go on to create an array to fill those rows etc... but instead I next add another UIPickerView like so:
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIPickerView *myPickerView = [[UIPickerView alloc] initWithFrame:CGRectMake(0, 200, 375, 200)];
myPickerView.delegate = self;
myPickerView.showsSelectionIndicator = YES;
[self.view addSubview:myPickerView];
UIPickerView *my2PickerView = [[UIPickerView alloc] initWithFrame:CGRectMake(0, 400, 375, 200)];
my2PickerView.delegate = self;
my2PickerView.showsSelectionIndicator = YES;
[self.view addSubview:my2PickerView];
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
NSUInteger numRows = 5;
return numRows;
}
Now I have two pickerview controllers and they BOTH have five rows. My question is how do chose to which pickerview the method applies and also could anyone explain why does the method apply to all pickerviews in the project? Thanks.

You only have one delegate method for two PickerViews ; that's something I don't like with iOS but you don't really have a choice here.
You have to if-statement yourself out of this.
The pickerView parameter in the delegate method is the pickerview that is being assigned the number of rows.
Note that this is valid for any of the usual delegate methods for iOS, wether it's the numberOfRows of your pickerview, or your tableview, or your collectionView, or any delegate method that has the view in parameter.
The easy understandable way is to have your pickerview as fields of your class (or properties), and simply compare the parameter with it.
#interface ViewController ()
#property (weak, nonatomic) UIPickerView *_mySexyPickerView;
#property (weak, nonatomic) UIPickerView *_myOtherPickerView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_mySexyPickerView = [[UIPickerView alloc] initWithFrame:CGRectMake(0, 200, 375, 200)];
_mySexyPickerView.delegate = self;
_mySexyPickerView.showsSelectionIndicator = YES;
[self.view addSubview:_mySexyPickerView];
_myOtherPickerView = [[UIPickerView alloc] initWithFrame:CGRectMake(0, 400, 375, 200)];
_myOtherPickerView.delegate = self;
_myOtherPickerView.showsSelectionIndicator = YES;
[self.view addSubview:_myOtherPickerView];
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
if (pickerView == _mySexyPickerView){
return 2;
}
if (pickerView == _myOtherPickerView){
return 19;
}
return 0;
}

Related

What are the best practices for refactoring methods in Objective-C

How can I get this kind of method refactored better?
This is just a sample in my objective-c project, and I am trying to do it all programmatically
Im not sure what the best practices would be, create a protocol? an extension? or refactor further down to additional methods?
- (void)viewDidLoad {
[super viewDidLoad];
// Label to be changed
label = [[UILabel alloc]init];
label.text = #"Changed with code!";
[self.view addSubview:label];
// Label Constraints
label.translatesAutoresizingMaskIntoConstraints = NO;
[label.topAnchor constraintEqualToAnchor: self.view.safeAreaLayoutGuide.topAnchor constant:50].active = YES;
[label.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor constant:20].active = YES;
[label.trailingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor constant:-20].active = YES;
}
As an example you could create a UIView subclass that creates the label and makes the layout.
#interface MyView: UIView
#property (nonatomic, strong, readonly) UILabel *label;
#end
#interface ViewController: UIViewController
#property (nonatomic, strong, readonly) MyView *myView;
#end
#implementation MyView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_label = [[UILabel alloc] initWithFrame:CGRectZero];
_label.translatesAutoresizingMaskIntoConstraints = NO;
UILayoutGuide *safeAreaLayoutGuide = self.safeAreaLayoutGuide;
[NSLayoutConstraint activateConstraints:#[
[label.topAnchor constraintEqualToAnchor: safeAreaLayoutGuide.topAnchor constant:50],
[label.leadingAnchor constraintEqualToAnchor:safeAreaLayoutGuide.leadingAnchor constant:20],
[label.trailingAnchor constraintEqualToAnchor:safeAreaLayoutGuide.trailingAnchor constant:-20]
]];
}
return self;
}
#end
#implementation MyViewController
- (void)loadView {
self.view = [[MyView alloc] initWithFrame:CGRectZero];
}
- (MyView *)myView {
return (id)self.view; // it makes view to load regardless which
// property do you use – `view` or `myView`
}
- (void)viewDidLoad {
[super viewDidLoad];
self.myView.label.text = #"Changed with code!";
}
#end

Using a block in Object-C, I get "Thread 1:EXC_BAD_ACCESS(code=1,address=0x0)"

I want to add GestureRecognizer on an imageView and use a block in the clickAction.
But when I run it, I get this error in the block:
Thread 1:EXC_BAD_ACCESS(code=1,address=0x0)
#import "blockImage.h"
#implementation blockImage
-(instancetype)init{
if (self = [super init]) {
self = [[blockImage alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
self.tag = 10;
self.backgroundColor = [UIColor redColor];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tapActionWithBolck:)];
[self addGestureRecognizer:tap];
}
return self;
}
-(void)tapActionWithBolck:(void(^)(NSInteger idx))completion{
completion(10);
}
#end
and
#import "ViewController.h"
#import "blockImage.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
blockImage *img = [[blockImage alloc] init];
[self.view addSubview:img];
}
#end
You can not use method like this with UITapGestureRecognizer
-(void)tapActionWithBolck:(void(^)(NSInteger idx))completion;
it should be like that
-(void)tapAction:(UIGestureRecognizer *)sender;
or
-(void)tapAction;
If you want to use block on tap, you should keep it in variable (in init for example) and than call it in method. But keep in mind about retain cycles.

iOS: Styling multiple textfields

I have multiple UITextFields with the same/similar styling but I'm styling them individually. How would I style them all in on go?
example of what I'm doing now:
textfield1.layer.borderWith = 2;
textfield2.layer.borderWith = 2;
textfield3.layer.borderWith = 2;
Here's something I've used in the past:
UITextField * textField1;
UITextField * textField2;
UITextField * textField3;
for (UITextField * textField in #[textField1, textField2, textField3]) {
textField.layer.borderWidth = 2;
}
If you're doing extensive edits to create a highly customized textField, it might be better to use a subclass. If you've just got a few additions you want applied to each textfield, this should be fine.
As per your request, because I'm feeling generous. Here's an example of a UITextField subclass.
MyTextField.h
#import <UIKit/UIKit.h>
#interface MyTextField : UITextField
#end
MyTextField.m
#import "MyTextField.h"
#implementation MyTextField
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.layer.borderWidth = 2;
// Blue For Demonstration
self.layer.borderColor = [UIColor blueColor].CGColor;
// add whatever else you want to customize here ....
}
return self;
}
////////
////// ** Include Custom Methods You Might Need **
////////
#end
Then, in whatever ViewController or View you want to use it, add this:
#import "MyTextField.h"
Then, you can launch MyTextField class Textfields like this:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
MyTextField * textField1 = [MyTextField new];
textField1.frame = CGRectMake(10, 30, 100, 30);
[self.view addSubview:textField1];
MyTextField * textField2 = [MyTextField new];
textField2.frame = CGRectMake(10, 70, 100, 30);
[self.view addSubview:textField2];
MyTextField * textField3 = [MyTextField new];
textField3.frame = CGRectMake(10, 110, 100, 30);
[self.view addSubview:textField3];
}
And you'll get this:
How about --
for (UIView *view in [self.view subviews]) {
if ([view isKindOfClass:[UITextField class]]) {
UITextField *textField = (UITextField *)view;
textField.layer.borderWidth = 2.0;
}
}
That way you will not even have to have outlets of textFields in the view. But this is, of course, only applicable if you want to apply same styles to every textField in the view.
If the textfields have outlets, create an IBOutletCollection like so in your .h:
#property (strong, nonatomic) IBOutletCollection(UITextField) NSArray *textFields;
Then, in your .m:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
for(UITextField *textField in self.textFields) {
textfield.layer.borderWith = 2;
// add more here as needed
}
}

Not to dismiss when I tap on UIPickerView

I have a UIPickerView where I have a Male and a Female option.
What I have is when I click on label, the UIPickerView is shown, and based on the selection in UIPickerView, the same text is shown on label.
All is working well, but tapping on UIPickerView is not working properly.
When I tap on UIPickerView,
For iOS6, UIPickerView gets dismissed.
For iOS7, UIPickerView DOESN'T get dismissed.
So what I wanted to do is to not dismiss UIPickerView when I click on it.
Any idea how to do that for iOS 6?
Dropbox link
Code
.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController<UIPickerViewDelegate, UIPickerViewDataSource>
#property (retain, nonatomic) IBOutlet UIPickerView *myPicker;
#property (retain, nonatomic) IBOutlet UILabel *myLabel;
#property (retain, nonatomic) IBOutlet NSMutableArray *arrayGender;
#end
.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize myPicker, myLabel, arrayGender;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
arrayGender = [[NSMutableArray alloc] init];
[arrayGender addObject:#"Male"];
[arrayGender addObject:#"Female"];
myLabel.text = #"Choose gender...";
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(hideAllKeyboards)];
tapGesture.cancelsTouchesInView = NO;
[self.view addGestureRecognizer:tapGesture];
myLabel.userInteractionEnabled = YES;
UITapGestureRecognizer *tapGestureRecognizergenderLabel = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(genderLabelTapped)];
tapGestureRecognizergenderLabel.numberOfTapsRequired = 1;
[myLabel addGestureRecognizer:tapGestureRecognizergenderLabel];
[tapGestureRecognizergenderLabel release];
myPicker.hidden = NO;
}
-(void) genderLabelTapped {
NSLog(#"genderLabelTapped");
[myPicker reloadAllComponents];
myPicker.hidden = NO;
}
-(IBAction)hideAllKeyboards {
NSLog(#"hideAllKeyboards");
myPicker.hidden = YES;
}
- (void)viewWillAppear:(BOOL)animated
{
NSLog(#"viewWillAppear");
[super viewWillAppear:animated];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void) dealloc {
[myPicker release];
[myLabel release];
[super dealloc];
}
- (IBAction)takeMeBack:(id)sender {
[self.navigationController popViewControllerAnimated:YES];
}
#pragma mark -
#pragma mark PickerView DataSource
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 1;
}
-(UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view
{
NSLog(#"custom data..");
if (IS_DEVICE_RUNNING_IOS_7_AND_ABOVE()) {
// NSLog(#"changing font...");
UILabel *label;
// = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 233, 44)]; // your frame, so picker gets "colored"
label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 233, 44)];
label.textColor = [UIColor whiteColor];
label.font = [UIFont fontWithName:#"Trebuchet MS" size:14];
label.textAlignment = NSTextAlignmentCenter;
label.text = [arrayGender objectAtIndex:row];
return label;
} else {
// NSLog(#"changing font...");
UILabel *label;
// = [[UILabel alloc] initWithFrame:CGRectMake(40, 0, 193, 44)];
label = [[UILabel alloc] initWithFrame:CGRectMake(40, 0, 193, 44)];
label.backgroundColor = [UIColor clearColor];
label.textColor = [UIColor blackColor];
label.font = [UIFont fontWithName:#"HelveticaNeue-Bold" size:18];
label.font = [UIFont fontWithName:#"HelveticaNeue-Bold" size:14];
label.text = [arrayGender objectAtIndex:row];
return label;
}
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
return [arrayGender count];
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
return [arrayGender objectAtIndex:row];
}
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row
inComponent:(NSInteger)component
{
myLabel.text = [NSString stringWithFormat:#"%#", [arrayGender objectAtIndex:row]];
myPicker.accessibilityValue = [NSString stringWithFormat:#"%#", [arrayGender objectAtIndex:row]];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
- (BOOL)shouldAutorotate
{
return NO;
}
#end
I know because of gesture recognizer, picker is getting hided in iOS6. The problem is why it is not getting hided in iOS7?
Did you write this yourself if you just copy-pasted it from somewhere and you didn't know what it does so you came to Stack Overflow with close to zero research done? By close to zero research, I mean you didn't even read the code...
What do you think these lines do?
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(hideAllKeyboards)];
tapGesture.cancelsTouchesInView = NO;
[self.view addGestureRecognizer:tapGesture];
-(IBAction)hideAllKeyboards {
NSLog(#"hideAllKeyboards");
myPicker.hidden = YES;
}
It turns out, when a touch happens (on the pickerview) in iOS7, the view that catches the tap is of class UIPickerTableViewWrapperCell, while in iOS6, it's of class UIPickerTableView (if not tapping on a row) or UITableViewCellContentView (when tapping on a row). My guess is, the later two let the tap pass through as if the tap happened on their superview (in your case, self.view). <- The last sentence is just a guess, not for sure.
The way you can make sure the picker only gets hidden in case the tap happened on self.view is to set a delegate self as delegate to tapGesture, then implement the gestureRecognizer:shouldReceiveTouch method:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if (touch.view == self.view) {
return YES;
} else {
return NO;
}
}

2 UIPickerViews each having its own UILabel to display value from NSMutableArray

I'm having 2 UIPickerViews and two UILabels in my view and the UIPickerViews are populated with numbers from an NSMutableArray.
The pickers need to send there chosen value to there assigned label. Example:
_pickerView1 (selected "18")
_pickerOutputLabel1 (shows "18")
_pickerView2 (selected "7")
_pickerOutputLabel2 (shows "7")
I can't get this working, _pickerView2 also sends its value to _pickerOutputLabel1 instead of _pickerOutputLabel2.
I've tried a couple of things but i can't figure out how to get it to work.
This is the code (i removed my attempts to fix the issue so it atleast compiles :)
//header file
#import <UIKit/UIKit.h>
#interface UIPickerViewAndLabelsViewController : UIViewController <UIPickerViewDelegate, UIPickerViewDataSource> {
NSMutableArray *nrArray;
IBOutlet UIPickerView *_pickerView1;
IBOutlet UIPickerView *_pickerView2;
UILabel *_pickerOutputLabel1;
UILabel *_pickerOutputLabel2;
}
#property (nonatomic, retain) IBOutlet UIPickerView *pickerView1;
#property (nonatomic, retain) IBOutlet UIPickerView *pickerView2;
#property (nonatomic, retain) IBOutlet UILabel *pickerOutputLabel1;
#property (nonatomic, retain) IBOutlet UILabel *pickerOutputLabel2;
#end
//implementation file
#import "UIPickerViewAndLabelsViewController.h"
#implementation UIPickerViewAndLabelsViewController
#synthesize pickerView1 = _pickerView1;
#synthesize pickerView2 = _pickerView2;
#synthesize pickerOutputLabel1 = _pickerOutputLabel1;
#synthesize pickerOutputLabel2 = _pickerOutputLabel2;
/*
// The designated initializer. Override to perform setup that is required before the view is loaded.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
*/
// Implement loadView to create a view hierarchy programmatically, without using a nib.
/*
- (void)loadView {
}
*/
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
_pickerOutputLabel1 = [[UILabel alloc]initWithFrame:CGRectMake(400, 120, 50, 50)];
[self.view addSubview:_pickerOutputLabel1];
_pickerOutputLabel2 = [[UILabel alloc]initWithFrame:CGRectMake(400, 320, 50, 50)];
[self.view addSubview:_pickerOutputLabel2];
nrArray = [[NSMutableArray alloc] init];
for (int i=0;i<20+1;i++) {
[nrArray addObject:[NSString stringWithFormat:#"%d", i]];
}
_pickerView1 = [[UIPickerView alloc] initWithFrame:CGRectMake(500, 120, 100, 162)];
_pickerView1.delegate = self;
_pickerView1.dataSource = self;
_pickerView1.showsSelectionIndicator = YES;
_pickerView1.transform = CGAffineTransformMakeScale(0.8, 0.8);
[self.view addSubview:_pickerView1];
[_pickerView1 release];
[_pickerView1 selectRow:0 inComponent:0 animated:NO];
_pickerOutputLabel1.text = [nrArray objectAtIndex:[_pickerView1 selectedRowInComponent:0]];
_pickerView2 = [[UIPickerView alloc] initWithFrame:CGRectMake(500, 320, 100, 162)];
_pickerView2.delegate = self;
_pickerView2.dataSource = self;
_pickerView2.showsSelectionIndicator = YES;
_pickerView2.transform = CGAffineTransformMakeScale(0.8, 0.8);
[self.view addSubview:_pickerView2];
[_pickerView2 release];
[_pickerView2 selectRow:0 inComponent:0 animated:NO];
_pickerOutputLabel2.text = [nrArray objectAtIndex:[_pickerView2 selectedRowInComponent:0]];
[super viewDidLoad];
}
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)_pickerView1;
{
return 1;
}
- (void)pickerView:(UIPickerView *)_pickerView1 didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
_pickerOutputLabel1.text= [nrArray objectAtIndex:row];
}
- (NSInteger)pickerView:(UIPickerView *)_pickerView1 numberOfRowsInComponent:(NSInteger)component;
{
return [nrArray count];
}
- (NSString *)pickerView:(UIPickerView *)_pickerView1 titleForRow:(NSInteger)row forComponent:(NSInteger)component;
{
return [nrArray objectAtIndex:row];
}
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[super dealloc];
}
#end
I'm trying for 3 days and i'm stuck.
Thanks in advance.
In the UIPickerView delegate methods, you've named the pickerView parameter "_pickerView1". Naming that parameter the same as the instance variable does not mean the delegate method will be called only for that picker. It just becomes the local name for whatever picker calls the delegate method.
Since you've set the delegate for both the pickers to be self, both the pickers call the same methods.
To tell which picker is making the call, a couple of ways are:
Set a different tag value for each one when creating them and check the tag in the delegate method (eg. _pickerView1.tag = 1; and in the delegate method: if (pickerView.tag == 1)... )
Or, compare directly against the instance variable. For example:
- (void)pickerView:(UIPickerView *)pickerView //<-- std name as in doc
didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
if (pickerView == _pickerView1)
// Above:
// "pickerView" is the picker in which a row was selected
// "_pickerView1" is the actual instance variable
_pickerOutputLabel1.text = [nrArray objectAtIndex:row];
else
_pickerOutputLabel2.text = [nrArray objectAtIndex:row];
}
Also, you have IBOutlet in front of the control declarations but then you create them programmatically. If you are using Interface Builder to create the controls, don't re-create them in code. If you're not using IB, remove the IBOutlet.
you can also use this:
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
if( [pickerView isEqual: picker ]){
firststr = [firstArray objectAtIndex:row];
}
if( [pickerView isEqual: pickerAnother ]){
secondstr = [secondArray objectAtIndex:row];
}
}