Being new to objective-C coding I started out writing a basic app, fully programmatically (not using storyboards or xib) in one file, my AppViewController h and m files.
Everything worked lovely.
So then I wanted to break up the mass of code by subclassing sections, and everything went well apart from the UIPickerView. In fact simply commenting out the [background addSubview:colorPicker]; seemed to totally fix the issue. I never found the answer online so I proceeded to make a new document to replicate said issue.
So here goes:
UIPickerViewController.h
#import <UIKit/UIKit.h>
#import "Picker.h"
#interface UIPickerViewController : UIViewController
#end
Simply imports my new class.
UIPickerViewController.m
#import "UIPickerViewController.h"
#interface UIPickerViewController ()
#end
#implementation UIPickerViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *superview = self.view;
int height = superview.bounds.size.height;
int width = superview.bounds.size.width;
CGRect popupRect = CGRectMake(0, 0, width, height);
UIView *popup = [[UIView alloc]initWithFrame:popupRect];
popup.tag = 8;
[superview addSubview:popup];
Picker *picker = [[Picker alloc]initWithFrame:popupRect];
[picker viewAddTypeScreenToView:superview];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#end
Sets up a new view with a tag (so that i could reference it later with my new class)
Then actions a method from my new class to populate my new view.
Picker.h
#import <UIKit/UIKit.h>
#interface Picker : UIView
<UIPickerViewDataSource,UIPickerViewDelegate>
{
UIPickerView *colorPicker;
NSMutableArray *colorsArray;
}
#property (nonatomic, retain) UIPickerView *colorPicker;
#property (nonatomic, retain) NSMutableArray *colorsArray;
#property (strong,nonatomic) UILabel *myValue;
-(void)viewAddTypeScreenToView:(UIView*)superview;
#end
Setting up my variables and accessible method.
Picker.m
#import "Picker.h"
#implementation Picker
#synthesize colorsArray;
#synthesize colorPicker;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
-(void)viewAddTypeScreenToView:(UIView*)superview
{
UIView *baseView =[superview viewWithTag:8];
int height = baseView.bounds.size.height;
int width = baseView.bounds.size.width;
CGRect fullScreen = CGRectMake(0, 0, width, height);
UIView *background = [[UIView alloc]initWithFrame:fullScreen];
background.backgroundColor = [UIColor blackColor];
colorsArray = [[NSMutableArray alloc] initWithObjects:#"Red",#"Blue",#"Yellow",#"Green",nil];
CGRect myPickerRect = CGRectMake(10, 70, (width/2)-40, 200);
colorPicker = [[UIPickerView alloc]initWithFrame:myPickerRect];
colorPicker.dataSource = self;
colorPicker.delegate = self;
colorPicker.showsSelectionIndicator = YES;
[colorPicker selectRow:2 inComponent:0 animated:YES];
CGRect labelFrame = CGRectMake(10, 10, 180, 50);
_myValue = [[UILabel alloc]initWithFrame:labelFrame];
_myValue.textColor = [UIColor redColor];
_myValue.text = #"select colour";
[background addSubview:_myValue];
[background addSubview:colorPicker];
[baseView addSubview:background];
}
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
return 1;
}
-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
return colorsArray.count;;
}
-(NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
return colorsArray[row];
}
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
_myValue.text = [NSString stringWithString:colorsArray[row]];
}
#end
And finally the initiation called by the method in the picker class file.
This gives me an error along these lines
-[UITableViewCellContentView pickerView:titleForRow:forComponent:]: unrecognized selector sent to instance 0x8f2b000
2014-03-19 10:29:48.407 Briefcase[1800:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UITableViewCellContentView pickerView:titleForRow:forComponent:]: unrecognized selector sent to instance 0x8f2b000'
Which i've read is to do with either the datasource, or ARC systems, however none of the responses that I have found relate to or work with the type of set up that I have above. I'm sure it's something really simple but after a few days of failed searching, it's officially driving me crazy.
The problem is most likely that the instance of Picker that is being created in UIPickerViewController is never added to the view hierarchy and thus gets released prematurely (provided we're talking about a project using ARC here).
This leads to the pickerview's delegate and datasource becoming invalid and, basically, pointing at any random object. That's what is causing your crash: A message to your delegate cannot be delivered because the delegate is dead already. The picker still keeps a pointer which used to point at the delegate, but which has become invalid and points at a random object now, in this case a table view cell, which basically doesn't know what to do with this message and crashes.
The problem should go away if you add Picker *picker as an ivar or a retaining / strong property to UIPickerViewController.h - this will retain the picker beyond the scope of the viewDidLoad method and should keep it alive.
But that would be just a workaround, the real problem is your overall design. You said you're new to objective-c and indeed, it looks like you lack a basic understanding of iOS view and view controller hierarchies and, to some degree, the concept of object oriented programming. You might want to dig into something more basic before trying to fix your code because, quite frankly, it should be rather re-written than fixed.
I'd be happy to provide you with suggestions about how to structure your code, but please provide some information about what functionality you'd like to achieve first.
Edit (in response to your comment):
As a rule of thumb, do not spread functionality over several classes unless necessary. For objects, which serve a rather infrastructural purpose, like a specialized textfield or a pickerview, always ask yourself: "If I would like to reuse that object in another project, would that be as easy as using any other existing object, like, for example, UILabel?" If the answer is "No", then something is wrong. Ideally, interface objects are self-contained and to use them, you just invoke them, add them to a view and tell them, which text to display or which options to offer. If that information is subject to change or if the object needs to interact with other parts of your code, make use of delegation and protocols. Under no circumstances should the functionality of your object be tied to hard coded values or rely to some view to have a certain tag.
If you subclass UIView, the resulting object should behave like any other instance of UIView. It should be added to the view hierarchy by you or some object, but it shouldn't add or remove itself. If it works without being added to the view hierarchy at all, something is wrong. A view serves the purpose of being a part in your interface and all the logic it contains should work to that end, not more, not less.
Normally, interface objects should not interfere with one another. If something happens to one object (button pressed, option selected, text changed...) and another object is supposed to reflect that change, it is the view controllers responsibility to make that happen. The view controller is the place where the logic happens. If there is a task which requires a lot of complex logic, it might be a good idea to encapsule that logic into a purpose build class. One such example would be a class which manages network connections. This class should be again self contained: If the view controller needs some remote information, it asks your network class. Once your network class has that information (or failed to retrieve it), it reports back to your view controller. The view controller then updates the interface - under no circumstance should the networking class contain code which affects the interface.
It is important to understand that you could very well ignore these rules and still end up with a working app. And in some cases, the "direct" way may appear to be easier to implement and thus may look very tempting. But you'll pay the price later - once you start debugging your code. If your picker does not behave the way it should, you need to look into several places and wrap your mind around several objects, just to make one interface object behave right. And likely you will break one functionality while fixing the other.
So, try to make it right from the start, even though it requires more planning and learning. Trust me, it pays out, I started out just like you several years ago ;)
Related
I am new to iOS development and I am currently reading the book : iOS Programming (Objective C) by Big Nerd Ranch.
I am confused as in where to initialize subviews such as UIButtons, UIImageView while creating views programtically:
Should the intialization be done in the Main UIView i.e in the
initWithFrame method and maintain a additional weak reference to the subview in the UIView.
or
should I do it in the UIViewControllers loadView method and maintain a weak reference to the subview in the uiviewcontroller (Same approach used while creating UIVew using the interface builder).
I have seen both the approaches being used in various stackoverflow posts but no post that explains which approach is the right one.
you can initialize as per your app's requirement. If any view or button or anything is part of initial setup of your app then you should initialize it in viewDidload.
Now, for example there is requirement like user press button and then new view will be created then you can initialize view in button's click method etc.
So, it's depends on your requirement.
Static views which will live from start to and of app should be initialize in viewdidload, because this is the first method getting called of viewcontroller.
hope this will help :)
It dependes on which architecture you are using. Apple raises the flag of Model-View-Controller, but in fact, UIViewControllers are the View.
For Example:
Let's say that you have a pretty LoginViewController. When you instantiate it, you will be doing something like
LoginViewController *loginVC = [[LoginViewController alloc] init];
At this point, no view is loaded. Your ViewController has just executed the init method, nothing else. When the system calls
loginVC.view
the first method to be executed will be
- (void)loadView;
there you should do exactly that, load your view. So, the approach i like is to have an additional LoginView.
- (void)loadView
{
// you should have a property #property (nonatomic, strong) LoginView *loginView;
self.loginView = [[LoginView alloc] init];
self.view = self.loginView;
}
and in the LoginView init method, you should put your code to build up the view.
However, you could eliminate LoginView, and instantiate all your subviews like this:
- (void)loadView
{
self.view = [[UIView alloc] init];
UIButton *button = [[UIButton alloc] initWithTargetBlaBlaBla...];
[self.view addSubview:button];
// add more fancy subviews
}
In my experience, the first approach is much cleaner than the second one. It also makes version control a lot easier (try to merge a xib, I dare you). I always use MyView.m to build the view (a.k.a setup constriants, style) and use MyViewController.m things like animations, lifeCycle. I like to think that MyView.m is the programatic xib, so anything that you can do with xibs, you should me able to do it inside your view.
Hope it helps!!
I have two scenes that I made in SpriteBuilder, one is Shop and the other is UpgradesNew. Shop is a CCNode layer and UpgradesNew is a CCNode layer. I have two CCScrollViews in the MainScene that load Shop and UpgradesNew respectively.
When one button in Shop is tapped, the label in UpgradesNew should change colors. I have been trying to implement this using delegates but it's not working.
Here's what I did...
In shop.h I set the protocol:
#protocol changeColorProtocol <NSObject>
#required
-(void)changeColor;
#end
Then I set the id
#property (nonatomic, retain) id <changeColorProtocol> delegate;
Here is the button that when clicked, should use changeColor. This is in shop.m
-(void) buyDiggerShibe {
[self.delegate changeColor];
[self didLoadFromCCB];
}
Now in UpgradesNew.h I made it adopt the protocol like this
#interface UpgradesNew : CCNode <changeColorProtocol>
And in UpgradesNew.m
I set delegate to self in ViewDidLoad.
Shop *shop = [[Shop alloc]init];
shop.delegate = self;
.
-(void)changeColor {
if (hasDigger == YES) {
shovelRequires.color = [CCColor greenColor];
NSLog(#"HEY HEY HEY");
}
}
I probably have parts of the delegate placed in the wrong area because I was trying to switch them around when it wasn't working, I'm not sure where they are supposed to go. I've watched multiple delegate tutorials and it just seems overly complicated, at least with what I am trying to do.
Any ideas?
EDIT:
Tried this.
I created a property in UpgradesNew
#property (strong, nonatomic) Shop *shop;
Then I synthesized it in the implementation and allocated it like this in didLoadFromCCB, instead of creating a new object:
self.shop = [[Shop alloc]init];
shop.delegate = self;
EDIT: This is how I am creating objects.
Drag a label into a layer. Identify it then define it in header as CCLabelTTF *label; That's it, thats all I do to create any object on the layer.
To create a layer like Shop or UpgradesNew, I hit New -> File -> Layer. That creates a new CCNode. Then I set the class of the CCNode, as shown in the picture the CCNode that is highlighted has a class of MainScene. If I want to establish a #property to that CCNode I just type the name in the box right below custom class and set it as doc root var, and then put it in the header as CCNode *MainScene. I don't do anything other than that.
I don't know anything about SpriteBuilder, so it's a bit hard to address your question. You might want to add SpriteBuilder to the title of your post so people who use that framework are likely to read it.
You need to explain how the 2 "CCNode layer"s are created, and how you link them together. In order for one object to have another object as a delegate, the delegate property has to be set somewhere. Where is that setup being done? Have you set a breakpoint at the line
[self.delegate changeColor];
To make sure that self.delegate is not nil?
Have you set a breakpoint in your changeColor method, or added a log statement, to see if it's being called? My guess is that self.delegate is nil, so the messages is being dropped on the floor (it's legal to send messages to nil in Objective-C. It just doesn't do anything.)
okay below is a standard example of creating a datepicker
- (void)viewDidLoad {
CGRect pickerFrame = CGRectMake(0,250,100,100);
UIDatePicker *myPicker = [[UIDatePicker alloc] initWithFrame:pickerFrame];
[myPicker addTarget:self action:#selector(pickerChanged:) forControlEvents:UIControlEventValueChanged];
[self.view addSubview:myPicker];
[myPicker release];
}
- (void)pickerChanged:(id)sender
{
NSLog(#"value: %#",[sender date]);
}
this is all good and well. I'm a little used to creating elements in IB so when I create an object programatically I'm not sure how to access the data.
What I mean is.. should I assign myPicker to a class property and then access it as _myPicker?
Or lets say I want to access the date inside of the pickerChanged method without calling another method. Should I assign an NSDate property and re-assign it every time the picker is changed?
I ran into some memory issues when I was trying to do it that way. I had another method grabbing _theDate, and it probably tried to access it at the same time pickerChanged was modifying it?
Anyway, what I'm getting at is "whats the proper workflow when creating things like action sheets, and pickers programmatically". When these things are changed, how should the resulting data be saved so the rest of the class can access it?
Bonus question:
Is there a difference between this?
for(UILabel *myLabel in view.subviews){
NSLog(myLabel.text);
}
and this? Do I need to check the class all the time if i know my view only contains a certain kind of object?
for((id) myLabel in view.subviews){
if([myLabel isKindOfClass:[UILabel class]){
UILabel *theLabel = myLabel;
NSLog(myLabel.text);
}
}
Generally, you will just define properties if you'll need to access them more than once. You can do this in the .m file's interface:
#interface MyObject()
#property (weak, nonatomic) UIDatePicker *myPicker;
#end
You will then be able to access it by either _myPicker or self.myPicker.
You shouldn't need another NSDate property in your class because you can access the set date at any time:
_myPicker.date
For your last question: the latter of the two is merely extra sanity checks. While you're writing your own code, and you should know what subviews you're adding in, it can't hurt to double check the type of the subviews incase anything should go wrong and you try to access selectors that don't exist. This is a larger programming question though and not necessarily objective-c or iOS specific.
The documented approach is to intercept the UIControlEventValueChanged event, as per your example.
You would then typically copy the [sender date] value to a property in your pickerChanged: method.
If the user hits a save button, then the object that presented the view containing the picker should be able to retrieve the selected date via the property.
It's not considered good practice to use isKindOfClass:. You should structure your code such that you always know what class you're dealing with.
Also, you should really switch to ARC so you don't need to worry about calling release
You need to declare a UIDatePicker property to hold one instance of your child controller
This is what you need to add in your .h file:
#property (strong, nonatomic) UIDatePicker *myPicker;
And then in your .m file you need to add a data source method for this date picker. something like what rdelmar has instructed above:
self.myPicker = [[UIDatePicker alloc] init];
This may sound silly, but read on...
I want to set the text of a UILabel from outside of a UIViewController that is instantiated by a storyboard. I need to make sure that the label property of the view controller is set when I set its text otherwise the label's text won't be set(because it won't be loaded yet to receive a text value).
Here's my current solution:
// Show pin entry
if (!self.pinViewController) {
// Load pin view controller
self.pinViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"pinScreen"];
self.pinViewController.delegate = self;
if (!self.pinViewController.view) {
// Wait for pin screen to fully load
}
[self.pinViewController setMessageText:#"Set a pin for this device"];
}
Initially I had a while loop that looped until the value of view was not nil, But it seems the very act of checking the view loads it(as mentioned here: http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40006926-CH3-SW37)
I tried using the isViewLoaded method with no success. It just looped forever.
I've gone forward with the above code as my current solution, but it feels wrong.
Is there a better way ensure a UIView has loaded?
I want to propose an alternative way where you don't have to rely on the availability of the view.
If you need to wait for the view to load before you can call other methods on your viewController you break encapsulation, because the viewController that calls your PinViewController has to know about the inner workings of your PinViewController. That's usually not a good idea.
But you could save objects like NSStrings in the PinViewController instance, and when the view of the PinViewController will appear you set its views according to the properties you have set before.
If you need to change the text of an label from outside your viewController you can also create a custom setter that sets the label.text for you.
Your .h
#interface PinViewController : UIViewController
#property (copy, nonatomic) NSString *messageText;
// ...
#end
And your .m
#implementation PinViewController
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.messageLabel.text = self.messageText;
}
// optional, if you want to change the message text from another viewController:
- (void)setMessageText:(NSString *)messageText {
_messageText = messageText;
self.messageLabel.text = messageText;
}
// ...
#end
viewDidLoad should solve this I guess.
http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html
I would rather see you change your logic and do it the way that #MatthiasBauch shows in his answer. However, to answer your actual question, you can simply set a view property in order to force it to load:
self.pinViewController.view.hidden = NO;
The problem is simple to explain but difficult for me to resolve. I have a property that is NEVER initialized.
First of all, I'm using the iCarousel custom class in order to display some images for my app. In one of its delegate methods (the one that it uses in order to know which view is going to show at some index), I use this code:
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView *)view
{
if(!view)
{
CustomController* controller = [self.storyboard instantiateViewControllerWithIdentifier: "identifier"];
//BTW, the CustomController is initialized properly. Its instance is not nil after the initialization.
controller.imageView.image = [UIImage imageNamed: "something.png"];
view = controller.view;
}
return view;
}
As you can see, the view that I show in my carousel is a custom view with its own controller. I initialize it using the storyboard method and then I just set the image in my imageView property, which is, obviously, an UIImageView.
Don't get excited and say that I'm not initializing my imageView, because I have a custom getter in my "CustomController" class. Like this:
//interface (.h)
...
#property (nonatomic, strong) UIImageView* imageView;
...
//implementation (.m)
...
#synthesize imageView = _imageView;
...
- (UIImageView*) imageView
{
if(!_imageView)
_imageView = [[UIImageView alloc] init];
return _imageView;
}
...
Believe it or not, even if I put a breakpoint in the "_imageView = [[UIImageVIew alloc] init];"... the program executes that line but the _imageView remains nil. ¿Why?
I don't want to know "How to set my property", so please don't give workarounds for this... what I want to know is "Why my property is never setted and remains nil always", what's am I doing wrong?.
I've also tried to use my imageView as an IBOutlet... but even if I link it to an imageView in the Interface Builder and check its value after the "viewDidLoad", it still remains nil.
P.S: Btw, I'm using ARC (yeah, I know is in the title... xD)
Well, it looks like the answer was what borrrden said, the problem was the LLDB debugger. Actually, my property was initialized but the debugger didn't detect it like that, if I change it to GDB I could see it wasn't nil after all. Furthermore, the reason why I had also issues with my child viewcontroller's outlets was because I didn't use the View Controller Container methods in iOS5 (DidMoveParentViewController and those ones).
Kinda tricky.