How you guys slim down your view controllers?, sometimes you end up implementing a lot of protocols in your view controller, so there’s a lot of code inside the controller itself. Reading about how to slim down view controllers in iOS I found that a common way is to move DataSources (http://www.objc.io/issue-1/lighter-view-controllers.html) to other class, but what about other delegates?, or if you create views by code?. First, I think about move each delegate to a NSObject class, so I try this:
self.locationManager.delegate = [[FRRYPetDescriptionViewControllerLocationDelegate alloc] init];
Then I ask in IRC and somebody suggest categories, so this is what I got so far:
// FRRYPetDescriptionViewController.h
#interface FRRYPetDescriptionViewController : UIViewController
#property (nonatomic) CLLocationManager *locationManager;
#property (nonatomic) TPKeyboardAvoidingScrollView *scrollView;
#property (nonatomic) UIView *contentView;
#end
// FRRYPetDescriptionViewController+Protocols.h
#interface FRRYPetDescriptionViewController (Protocols) <UITextViewDelegate, UIActionSheetDelegate, MFMailComposeViewControllerDelegate, UIGestureRecognizerDelegate, MKMapViewDelegate, UIViewControllerTransitioningDelegate, CLLocationManagerDelegate>
#end
// FRRYPetDescriptionViewController+UIAdditions.h
#interface FRRYPetDescriptionViewController (UIAdditions)
- (void)createScrollView;
- (void)createContentView;
#end
// FRRYPetDescriptionViewController+Callbacks.h
#interface FRRYPetDescriptionViewController (Callbacks)
#end
// FRRYPetDescriptionViewController+LocationAdditions.h
#interface FRRYPetDescriptionViewController (LocationAdditions)
#end
This makes me think, what about “private” methods?, do I need to declare all properties in the view controller header file?. What you guys think about this approach or there’s some common pattern to follow to not end with a fat controller?.
Thank you.
The link that you have referred to has beautiful explanation for less bulky controller's programming. But techniques are bit tricky unless you are a seasoned developer. You have asked about multiple techniques in your question. Please check my views on them below: -
Delegates - I personally prefer to keep the delegate code in my controller itself to avoid unwanted confusion specially when you are working with multiple scenarios within the same controller.
Create Views Programmatically - This is the portion where we can cut the large amount of code from UIViewController. Unless it is a single control such as a single label or button, we should make a custom UIView class and let it set all the common properties for view customisation. Controller should only invoke it with necessary parameters.
Properties in Header File - No, concept of data encapsulation says that we should only make those variables public which are required. Rest should move to private domain so that we external objects can't interfere with the class object functionality. So you should declare these properties in class extension (inside .m file). Moreover it is not required to have all variables as properties, when they are private you can declare them as instance variables as property declaration does nothing but to create the getter/setter for that variable.
Private Methods - Same goes to methods as well. If it doesn't have to be exposed to other objects; it should not be in header file. Keep it in extention as private method.
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).
I would like to know which place is best for placing the IBOutlets from storyboards:
a) In the header file (.h)
b) In a class extension created on the .m file
Thank you
Regards
You have to keep in mind that .h is a public header.
So place your IBOutlet's there if they should be accessible by other classes.
However, even though you can do that. I would say that exposing the IBOutlet's in a public header is not a good practice (From object orientation perspective) since you are exposing some implementation details that should be only visible to whom is concerned about.
In short, placing the IBOutlet's in a class extension in the .m is a good practice.
From Apple's Resource Programming Guide: Nib Files:
Outlets are generally considered private to the defining class; unless there is a reason to expose the property publicly, hide the property declarations a class extension.
Class extension is the best place if you don't want to expose that outlet publicly. Your .h should be neat and clean and should only contain those methods or properties which are public (available for other programmers).
In this way you won't confuse your teammate by not having unnecessary ivars and methods deceleration in .h file
It's all about managing the code and making less confusion, otherwise there are no private methods/properties in Objective-C
Also if you check any sample of apple they follow the same pattern. e.g. LoadingStatus.m has code
#import "LoadingStatus.h"
#interface LoadingStatus ()
#property (nonatomic, strong) UIActivityIndicatorView *progress;
#property (nonatomic, strong) UILabel *loadingLabel;
#end
Assuming that this is for a view controller, option b is better as you shouldn't be exposing the outlets publicly for other classes to directly interact with. They should be considered your private knowledge. Your controller should expose a different and more appropriate interface.
If it's a view it's a bit more grey how you should approach the problem as MVC would lead you towards exposing the outlets to allow the controller to use them. MVVM would lead you towards hiding the outlets so that the view is passed a view model object and internally configures the outlets.
The #interface can appear in both the .h file (public properties) and the .m file (private properties). The IBOutlets should be declared in the .m file.
If you are interested read this topic.
Cheers!
I have used #protocols many times but I think I have been doing it wrong all the time. They have worked always well, but now I want to improve my level so I am trying to do it the better I can.
I always have created a delegate like this:
#protocol CommentViewDelegate;
#interface LZCommentView : UIView
#property (assign, nonatomic) id <CommentViewDelegate> delegate;
#end
#protocol CommentViewDelegate
-(void)showAndHideCommentView;
#end
Now, I have seen that almost all the delegate methods that I see send their own object. Something like this:
-(void)showAndHideCommentView:(LZCommentView *)commentView;
What is the difference between what I did and this? Is one of them better than the other? I have seen that almost everyone who does this, does not use the object in the ViewController.
Another question is, should I use <NSObject> in the #protocol definition?
And the last one, what is better create the #property with assign or strong?
Thank you
Generally, the object that you pass to the delegate can be used so that the same delegate class can be used in different contexts. This gives you more flexibility in cases when a delegate class could potentially be reused.
For example, if showAndHideCommentView needs to interact with a view being shown or hidden, it has two ways of doing it:
Get the view as an argument, or
Reference the view directly, knowing that this delegate is attached to a particular view.
Example of the first approach:
#implementation MyDelegate
-(void)showAndHideCommentView:(LZCommentView *)commentView {
if (isShowing) {
[commentView doSomething];
}
}
#end
Example of the second approach:
#implementation MyDelegate
-(void)showAndHideCommentView {
if (isShowing) {
[self.commentView doSomething];
}
}
#end
The first approach is more flexible than the second one, because it lets you reuse the same code.
According to Apple, it’s best practice to define your protocols to conform to the NSObject protocol, so the answer to your second question is "yes".
As far as the third question goes, the best way to declare delegate properties is with the weak attribute to avoid retain cycles.
1) You should always make your protocol conform to the NSObject protocol. This lets you make use of all of the methods in that protocol.
#protocol CommentViewDelegate <NSObject>
2) Unless you have a good reason to do otherwise, most properties for delegates should be defined as weak. This avoids reference cycles and it ensure the delegate is automatically set to nil if the delegate object is deallocated.
#property (nonatomic, weak) id<CommentViewDelegate> delegate;
3) It's best to include the object in the protocol methods because it offers the most flexibility. It also allows a class to be the delegate of more than one instance. Then the class can tell which instance the protocol method is being called for. Think of a view controller handling multiple buttons or having two or more table views.
I've seen this in some areas of code that I am working with.
searchBar.delegate = (id<UISearchBarDelegate>)self;
My question is...
Why not just make the current class a delegate of UISearchBar by adding <UISearchBarDelegate> in the interface of the class?
Are there times when the above code is better than having the class be a delegate?.
Both ways work for me, but I'd like to learn why I may want to use one over the other.
The cast approach could be considered a cheat. It would be used if the delegate class didn't want to publicly declare the protocol conformance (but then why is it being publicly set as the delegate). Or if the delegate class didn't implement all of the required methods so declaring the protocol conformance would result in warnings.
Generally the better approach is to declare the protocol conformance (either publicly (.h) or privately (.m)).
If you are not using the UISearchBarDelegate in your .h like
#interface ScaryBugData < UISearchBarDelegate>
#property (strong) UISearchBar * searchBar;
#end
then in your .m file then you must do the following to silence the warning
searchBar.delegate = (id<UISearchBarDelegate>)self;
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 .....