I started Objective-C programming a couple weeks ago, so my understanding of how all these pieces fit together & the order they're all happening is still confusing to me. I'm trying to make a JSON API call to one of my apps using NSURLSession. That all works flawlessly, but I want to update a label with a piece of the data that's returned & anytime I look at/try to update the label, I get null.
Some of the SO posts I've found that are similar to my problem include: this, this, this, and this. Coming from the world of Ruby on Rails, I haven't had to deal with async concepts at all, but I know I'm close.
Here's the relevant snippet of code in question:
if (!jsonError) {
NSDictionary *skillBuildData = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"%#:", skillBuildNameLabel.text); // should say "Build Name" but returns null
NSLog(#"%#", skillBuildData[#"name"]); // correctly prints the result
NSLog(#"%#:", skillBuildNameLabel.text); // should have contents of skillBuildData[#"name"] but returns null
skillBuildNameLabel.text = skillBuildData[#"name"]; // obviously results in null, but I don't know why.
});
}
EDIT:
Not sure if it's relevant, but here's the bulk of my ViewController.h to give you an idea of the outlets & actions in this very simple app. One button, one method, the IBOutlet that links the button & JSON call method, and a label:
#interface ViewController : UIViewController {
IBOutlet UILabel *skillBuildNameLabel;
IBOutlet UIButton *getSkillBuildDataButton;
}
- (void)getSkillBuildDataById:(int) skillBuildId;
- (IBAction)buttonPressed;
It seems like I'm very close, I just can't see the link I'm missing. Thank you so much in advance!
EDIT 2:
Check out Ben Kreeger's comment to the response I marked as the answer. I didn't connect the actual label in my storyboard to the outlet I created in my ViewController.h. I had no idea you could drag the line from the element in a storyboard to an actual line of code. That was the missing piece. Looks like I have a lot more to learn about Xcode & Objective-C. Thanks to all who helped me out!
You may have more luck declaring your IBOutlets as properties (#property) instead of as instance variables (see this answer for why weak instead of strong).
#interface ViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *skillBuildNameLabel;
#property (weak, nonatomic) IBOutlet UIButton *getSkillBuildDataButton;
...
#end
Then you'll be able to reference them as self.skillBuildNameLabel and self.getSkillBuildDataButton in your implementation.
Beware that this self.x notation inside of a callback like that may lead to what's called a retain cycle. If this is the case, Xcode will warn you about this. Here's a bit on retain cycles for you.
Footnote: I rarely ever see (and never write) this syntax anymore for declaring instance variables.
#interface ViewController : UIViewController {
IBOutlet UILabel *skillBuildNameLabel;
IBOutlet UIButton *getSkillBuildDataButton;
}
Use properties instead.
You are doing the logging before setting the text. move
skillBuildNameLabel.text = skillBuildData[#"name"];
to the top of the async block, above the NSLog statements.
Related
There are at least 3 methods of creating an IBOutlet in Objective-C, for making iOS 10 App, in Xcode 8.
Method 1: in ViewController.h
#interface ViewController : UIViewController
#property (nonatomic, strong) UILabel *textLabel;
#end
Method 2: in the interface of ViewController.m
#interface ViewController () {
IBOutlet UILabel *textLabel;
}
#end
Method 3: in the interface of ViewController.m, using #property
#interface ViewController ()
#property (nonatomic, strong) UILabel *textLabel;
#end
Given that the textLabel has to be accessed & its text is needed to be updated frequently, which method is the correct way to do so?
That all depends on whether you need your outlet to be accessible to classes outside of the containing one; generally I would discourage this because it is good practice to keep your view controllers responsible for updating your UI and not pass this task around to other classes. With this being said, Method 3 would be the best option, however, if you do have to access your object from another class, then simply use Method 1 so it is exposed in your class header.
Method 2 utilises iVars rather than object properties and is not the proper way to declare outlets, it may even cause unexpected behaviour so it is best to avoid this method.
Your code contains no proper IBOutlet. Outlets are connections to Storyboard.
Method 1
This is a property. As it is in .h file, it can be reached from outside. The Objective-C pattern for public.
Method 2
This is an iVar. Do not use iVars if you do not have to.
Method 3
This is a property. As it is in .m file, it can not be reached from outside. The Objective-C pattern for private.
Method 4
A proper IBOutlet looks like this:
#interface ViewController ()
#property (nonatomic, weak) IBOutlet UILabel *label;
#end
It is a simple property. You have to decide if you put it in .h or .m file depending on whether or not you want to publish it.
The IBOutlet simply makes the property connect-able to Storyboard. It's an annotation for Xcode and does not alter the semantic of your code.
Edit 1:
As Sulthan correctly mentions in the comments:
In most situations the correct design pattern is to hide outlets because it's an implementation detail. External classes should not set data directly using views.
Edit 2:
Why "not to use iVars if you do not have to" (2)
Opinion based:
I consider it as good OOP practice to use getters & setters (and thus not to access the variables directly). Also code is easier to read as you know while reading what x = self.variable (property) and x = variable (local variable) are.
If you have to use iVars for some reason, it is common to (and I would recommend to) prefix the name with _. x = _variable (iVar).
My problem is a follows
I have a UIViewController subclass which holds a UISegmentedController and four tableviews that I layed out in interface builder.
#interface MultiTableHoldingView : UIViewController{
}
#property (strong, nonatomic) IBOutlet DataTV *dsDataTV;
#property (strong, nonatomic) IBOutlet EnviroTV *dsEnvironmentTV;
#property (strong, nonatomic) IBOutlet LocationTV *dsLocationTV;
#property (strong, nonatomic) IBOutlet Note_AnimalTV *dsNoteAnimal;
#property (strong, nonatomic) IBOutlet UISegmentedControl *diveElementSegmentController;
#property (strong, nonatomic) DiveSite* currentSite;
- (IBAction)diveElementSegmentControllerDidChange:(UISegmentedControl *)sender;
-(void) setFreshWaterColor;
-(void) setSaltwaterColor;
#end
setFreshWaterColor and setSaltWaterColour just set the background colour properties of the MultiTableHoldingView instances UIView and the four tableviews it contains. Both these method work fine when called from MultiTableHoldingView's viewDidLoad method. Heres one of them
-(void) setSaltwaterColor{
DLog(#"in set salt water colour");
self.view.backgroundColor= SaltWaterColor;
_dsLocationTV.backgroundColor=SaltWaterColor;
_dsDataTV.backgroundColor=SaltWaterColor;
_dsEnvironmentTV.backgroundColor=SaltWaterColor;
_dsNoteAnimal.backgroundColor=SaltWaterColor;
}
The other is the same except sets to FreshWaterColor - both are #define i have set up.
I use the segmentedController to turn the hidden properties of the various tableviews on and off. All nice and simple. The tableviews are pulling in their data. Working fine.
When selecting one of my tableview cells on one of the tableViews I want to change the background colour of both my tableview ( in fact all of my tableviews ) and the UIView that is the superview
self.superview.backgroundColor = FreshWaterColor;
works fine for reaching back and changing the instance of MultiTableHoldingView views background property but I want to call the instance of MultiTableHoldingView's setFreshWaterColor and setSaltwaterColor methods.
I have imported MultiTableHoldingViews header into the relevant tableview (EnviroTV), so it knows about it its superviews methods. But if I try to call either of the two methods on self.superview the methods do not show up and if i type them in full I get an the following error
no visible interface for 'UIView' shows the selector 'setFreshWaterColor'
So i checked what kind of object the superview was and its a "class of superview UIViewControllerWrapperView"
I search on this and its apparently "
This is a private view used by the framework. You're not supposed to modify it or whatsoever."
I'm obviously missing something here - how should i call the method on the instance of MultiTableHoldingView ?
Thanks in advance
Simon
Doh - its just delegation as danypata mentions in the comments - i've posted exactly how I did this as an answer below. Tried to make it as clear as possible how delegation works
THE SOLUTION
Step one - get more sleep before coding .
This really is basic objective-c stuff - I just went off at a tangent, looking for someway else to do it, getting confused by my discovery of UIViewControllerWrapperView along the way.
The solution, as danypata rightly suggests in the comments, is to use delegate -a common design pattern in Objective-C - just like you do, for example, when you use another class to supply tableview data
As I've been a numpty and wasted hours of my time today I'll try and make the 'how' clear for other relative newbies or people having an off day and not thinking straight.
In my case I set this up as follows
In my subview class's interface file - EnviroTV.h - I define the following protocol just before the #interface declaration
#protocol EnviroTVProtocol <NSObject>
-(void) setFreshWaterColor;
-(void) setSaltwaterColor;
#end
Then in the #interface section of the same file I add a property of type id which must conform the protocol I just declared .
#property (nonatomic, strong ) id<EnviroTVProtocol> colorChangeDelegate;
You make the type an id - a generic object - as you really don't care what kind of object is going to act as your delegate just that it implement the methods that you need it to run. When an object declares itself to implement a protocol its just promising to implement the method(s) that are required by the protocol
So, when I want to run the methods on the superviews class I call
[self.colorChangeDelegate setFreshWaterColor];
Or
[self.colorChangeDelegate setSaltWaterColor];
The final piece of the delegation pattern is to go into the class thats going to be the delegate (in this case my MultiTableHoldingView class ) and state that it conforms to the protocol
I do this in the MultiTableHoldingView.h file
Changing this line :
#interface MultiTableHoldingView : UIViewController
into this line :
#interface MultiTableHoldingView : UIViewController <EnviroTVProtocol>
means this class promises to implement all the required methods of the EnviroTVProtocol.
Luckily I had already written the two methods. So when I compiled it ran correctly
Newbies - don't be afraid of delegation - its awesome and not as complex as you first imagine it to be
Meanwhile, if anyone can explain what UIViewControllerWrapperView is .....
How can I control the number of multiple UITableView's at build-time, using a single #define number?
My app presently needs 4 UITableView's. Later, the number will increase, so I want to control this at build-time with a single #define of how many.
But I get an error when I use an array in the #property declaration:
#define TOTAL_TX_CHANNELS 4
#interface blah() <UITableViewDataSource, UITableViewDelegate CBPeripheralDelegate>
{
}
#property (strong, nonatomic) UITableView* channel_tableView[ TOTAL_TX_CHANNELS ] ;
What's the trick? Should I use an NSArray or something?
Yes, use NSArray.
If it's configured in Interface Builder you could use:
#property (weak, nonatomic) IBOutletCollection (UITableView) NSArray * tableViews;
Why exactly do you need to control the # of UITableViews?
If you must do this, you could do some mathematical operation.
int adder = 4;
int currentNumberOfTvs = 4;
//Do some logic, that will add the number of current table views
if (...) //Test if user has added, or however, if another table view was added,
{
adder++;//now adder = 5. So you have a current count of how many table views there are
}
Hope that helps:)
I agree with the others that this is a terrible implementation. Wain's answer is very good.
However, still it is possible to implement it this way, just make sure to declare the property as
#property (assign, nonatomic) UITableView** channel_tableView;
Reference counting works only on Obj-C objects but you are creating a C array here.
Note that you have to use malloc to allocate the storage.
If you don't want to malloc, implement it as an ivar:
#interface Blah {
UITableView* channel_tableView[4];
}
#end
The solution was not to use an a property array, but to use an UITextView array at implementation scope, the .m...
UITextView* channel_pipe_textview[ TOTAL_TX_CHANNELS ];
Then, in function viewDidLayoutSubviews I created a UITableView for each channel, and added the standard functions for initializing the cells, all by following Apple's excellent UITableView programming guide.
It worked perfectly, completely independent of storyboard: I get four vertical side-by-side columns displaying any kind of images and text details regarding every device on the channel, with the ability to scroll, which allows any number of devices per channel.
The Apple UITableView Programming Guide also has tutorial and sample code for programmatically generating UITableViews solely at run-time.
I have been developing in xCode for exactly 3 days now. The last time I did UI development it was in Windows 3.1 with the Petzold book. Regardless of that I have my first iOS app up and running and it basically does what I need. I have two sliders to select hue and saturation, and in response to them I dynamically draw a bunch of gradient shaded circles. I got that much running between the Hello World example and stackoverflow, including caching the gradient in CGLayer (so thanks to all the stackoverflow people). There is one little piece that I can't quite get right though:
I want to set the initial value of one slider to 1.0 instead of the default 0.5. I know I can do that in IB, but I prefer to code it and I think I'd like to move away from IB altogether. I don't really understand how it makes connections between things and I'd like to understand the code. I have code like this to try to set it:
- (void)viewDidLoad
{
NSLog(#"viewDidLoad");
[super viewDidLoad];
[hue_slider setValue:0.5];
[sat_slider setValue:1.0];
self.led_view.hue_slider_value=0.5;
self.led_view.sat_slider_value=1.0;
// Do any additional setup after loading the view, typically from a nib.
}
sat_slider still ends up in the middle instead of at the end (1.0 is the max value). From stackexchange reading I understand that I am probably calling this at the wrong time, that the slider hasn't really been loaded when viewDidLoad is called, and my initial value is overwritten by the one specified in IB. What I haven't seen though is where the call should be made. So the question:
Where in my program should I put
[sat_slider setValue:1.0];
so that it sets the initial value of the slider, overwriting the default in IB? Can someone explain the order of how things start up in an iOS program? And a pointer to online or printed books regarding iOS and Objective C programming would be great.
Edit
When I check the value of sat_slider it is nil. So that means a connection is missing? I dragged it in the storyboard and created an IBOutlet in addition to an action.
#interface led_testViewController : UIViewController
- (IBAction)saturation_scroll:(id)sender;
- (IBAction)hue_scroll:(id)sender;
#property (retain, nonatomic) IBOutlet UISlider *hue_slider;
#property (retain, nonatomic) IBOutlet UISlider *sat_slider;
#property (strong, nonatomic) IBOutlet led_view *led_view;
#end
You may put the code in - (void)viewWillAppear:(BOOL)animated;
I followed the suggestions of ABC and NJones to check sat_slider and it was nil. There were properties for both sat_slider and hue_slider in the ViewController.h file, but something was still missing. I deleted the properties, re-dragged them in IB, and then I was able to set the slider position in viewDidLoad with no problems.
Check if sat_slider nil. Also make sure that it is properly connected in IB. If not, remove it and add it again in nib/storyboard. That should fix the issue.
When using IB in combination with assistant view you control-drag an element in the IB to the .h file and create an outlet. You can drag it to one of 2 place, either inside the variable declaration block or outside the block.
If you drag it inside the variable block you get something like this:
#interface MyViewController : UIViewController {
IBOutlet UIButton *foo;
}
dragging it outside the block gives you something like....
#interface ViewController : UIViewController {
}
#property (retain, nonatomic) IBOutlet UIButton *foo;
I've thought about how they are different and I'm a little confused. Ok, I understand synthesized properties do some magic and create instance variables at runtime (only on 64bit/ARM). So I believe I understand how the 2 options work.
What's the best option though? First option generates less code and seems simpler.
Second version offers public accessors/mutators, but I rarely access outlets from outside my class (and if I do, it's almost always with encapsulation). From the time I've started iOS work I've exclusively used this option.
Am I missing anything or should I make the switch to variable based outlets in most cases?
The ability to declare IBOutlet in the property declaration is relatively new #property (retain, nonatomic) IBOutlet UIButton *foo;
Previously, you had to declare IBOutlet UIButton *foo inside the curly braces and then synthesize the property. Now, declaring the IBOutlet in the curly braces is redundant.
You have two options to declaring the properties now. Option 1 is to declare it in your .h file, which will make it public. Alternatively, you can create a private interface in your .m file using:
#interface MYCLASS()
#end
and declare your properties there. This is my preferred way of doing it unless I need public access to that property (which should be the exception, not the norm if you are obeying MVC conventions).
Short answer: It doesn't make a much of a difference either way.
Long answer: If you want set/mutator methods, then drag outside of the block. If you don't care about methods and are just going to access the variables directly then putting them in as straight variables inside the block is probably the way to go.
Public visibility:
If you just specify the IBOutlet as a variable then you can use #private or #protected to prevent outside access. If you really want a #property for some reason you can still control public visibility by moving the property out of the .h and into a class extension in the .m file.
Conclusion: Myself, I'm sticking with the straight variable declaration and save the other options for when I need something extra.
IBOutlets are best inside of the block, unless you really plan on working with it in the .m file.
Remember, you can have both. The one inside of the variable block is essentially, in all basics, just for when you use it in IBActions.
The property can be used in the .m file for further customization.
Again, you can use both, it just depends on the extent you're using it.