Objective-C / Cocoa Touch: Is it good design to set label texts in the setter of the UILabel object? - objective-c

Let's say I have a two subclasses of UIViewController called MasterViewController and DetailViewController.
DetailViewController has a property of type NSNumber called level and a UILabel called levelLabel.
MasterViewController has a segue to DetailViewController called ToDetail. MasterViewController's prepareForSegue is like so
- (void)prepareForSegue:(UIStoryboardSegue)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"ToDetail"]) {
DetailViewController *detailVC = (DetailViewController *)segue.destinationViewController;
detailVC.level = [NSNumber numberWithInt:10]; // never mind the literal...pretend there was some algorithm for it
}
}
So then, in DetailViewController we implement the setter for levelLabel like so:
- (void)setLevelLabel:(UILabel *)levelLabel
{
if (levelLabel) {
_levelLabel = levelLabel;
_levelLabel.text = level.stringValue;
}
}
Is this good code design? Also, could you critique my code writing style? I pretty much wrote all this code on the fly so this is pretty much how I write code for the most part.
I thought of this question while showering because this is how I implement the setting of almost all the label texts that depend on a segue.

What follows is my own way of thinking about such relationships. Italics applies to your question.
You have the thing being controlled (the label) the controller (destination view controller) and the context it is being controlled within (the source view controller). This can also be expressed as model-view-controller, but I think thinking about a context can apply to much more specific and localised situations.
You should generally try to keep information flow going in one direction, from the context downwards. Objects should not have to be aware of the context in which they exist, ie they shouldn't have to ask for any information, they should be told everything they need to operate. So the source view controller should push the level to the destination view controller, the destination view controller should push this information to the label. This is what you already have, sort-of.
To build upon the above, not only should information flow in one direction, but I also try to ensure the relationships are causal, ie pushing information from one object to another should cause it to subsequently be pushed to the next object. Your code is not doing this which is probably why you have a bad feeling about it.
A more appropriate thing to do is set the text property of the label within the level setter, so that when you set or change the level, the label will update subsequently. The label may or may not be loaded so you will have to check whether it is using -isViewLoaded; -viewDidLoad is the appropriate place to set the text property upon first load.
(When I say 'push' that's just my way of thinking about setting properties or passing arguments because it implies directionality. It is really dependency injection. An example of pulling information would be delegates and data sources. But note here still the object isn't aware of any context, delegates and data sources are clearly defined as protocols, not classes, and usually within the same header file, and are themselves pushed onto the object from a surrounding context. So yes the object is asking for information, but on its own terms and from a system it has no knowledge of.)
Re coding style:
That's exactly how I write code but note Apple reserves the use of underscore prefixes

Related

Storyboard Segue Identifier naming conventions

I'm building a large storyboard and I was wondering if anyone has come up with helpful naming conventions for segue identifiers.
It looks like Apple just uses 'ShowX' in their examples where X is the name of the view it is showing. So far I prefer to use 'PushX' or 'ModalX' to keep track of which type of transition it is. Anyone have any other tricks or tips?
As with most things in programming, you can use any name you like.
But, as with most things in programming, names matter and good names are hard.
Here's how I name segues…
Good segue names
Name segues as you would name action methods. Name segues by what they will do. Good segue name examples:
addUser
showReport
editAccount
composeMessage
reviewChanges
Bad segue names
Avoid segue names that just describe what the segued to thing is or how it works.
Some examples of bad segue names!!:
Bad name 1! – segueUserDetailViewController – avoid this!
Bad name 2! – segueImageViewController – avoid this!
Bad name 3! – RecipeViewControllerToIngredientViewController – avoid this!
Why are these names bad?
These names are bad because they explicitly state their implementation. Instead of naming what they do, they name how they do it. This is a form of coupling.
So, if what you are doing is "showing a shopping basket", the fact that this happens to be done by presenting a ZZBasketViewController today, is totally irrelevant to the caller, and just burdens them with detail they don't care about. Perhaps tomorrow it'll be done with a ZZShoppingItemsViewController or an STSuperConfigurableStuffList.
So:
Use a name such as showShopping.
Avoid a name such as showBasketViewController.
A naming rule for all your programming
Usually the point of an abstraction is that a user doesn't and shouldn't know how the abstraction works. All they know is what it will do for them – what it means to them.
It is bad for any abstraction's name to needlessly specify how something must be done because then the caller prescribes how the callee must work.
This coupling will:
Burden the caller with knowledge it does not need.
Arbitrarily constrain the callee's implementation in a needless way.
Couple the caller to the callee in a pointless and costly way requiring maintenance or future removal.
If someone subsequently ignores the coupling and changes the implementation, then the given name begins to lie and will mislead a future programmer who looks at the code, unless the name is also changed.
Segues are abstractions. Segue names should not refer to the implementation.
Follow Cocoa's identifier convention
Both Swift and Objective C use camelCase for identifiers.
Names never contain _ or - characters.
With the exception of types, classes & protocols, all names should have a lower case first letter.
Name your segues in camelCase and give them a lower case first letter.
Uniqueness is not necessary
Segue names do not need to be unique within a storyboard. The names only need to be unique within a particular scene (view controller).
Ilea's answer mentions this, quoting from Ray's site:
It only has to be unique in the source scene; different scenes can use the same identifier.
…in fact, it often makes a lot of sense to have the same segue name in many scenes of a storyboard because you might be able to addLocation from a number of scenes.
Further tips for naming and working with Segues…
Scenes can have >1 segue going to the same other scene
This is something that I use a bit. You might have a view controller that can display and also edit account information: AccountVC. Nothing stops you having two or more segues from a scene that go to this same other view controller's scene: editAccount and showAccount. Your prepareForSegue:sender: can then use the segue ID to set up the AccountVC appropriately to either edit or just show.
Use the sender
Because segues are called with a sender, they feel very much like action messages.
Make use of the segue's sender to configure the destination view controller in your prepareForSegue:sender: implementation. This saves polluting your view controller with transitory state for this.
Here's an example of a table view delegate handling a tap:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
Account *const account = [self accountAtIndexPath: indexPath];
[self performSegueWithIdentifier: showAccount sender: account];
}
Which lets your prepare… method look like this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([showAccount isEqualToString: segue.identifier])
{
AccountViewController *accountVC = segue.destinationViewController;
accountVC.account = sender;
}
}
Avoid #"stringLiterals" in performSegue: calls
If you're asking "What's this showAccount? You mean #"showAccount", right?". A big tip is: Use file scope variables for your segue names. Do not use #"string literals". So, at the top of my view controllers there's often a block like this:
DEFINE_KEY(showReport);
DEFINE_KEY(showPDF);
DEFINE_KEY(shareReport);
The macro DEFINE_KEY is in my project's .pch and looks like this:
#define DEFINE_KEY(keyName) static NSString *const keyName = ##keyName
… it creates a const NSString* variable who's value is equal to its name. The static means that it's only available in this "compilation unit" and doesn't pollute the global name space at link time.
If you use a variable like this, you have the compiler on your side. You can't get a name wrong because then it won't build. Code completion will help you finish a name that you start. You can refactor a name as you would any other variable. Even the syntax highlighter is on your side!
Think of unwind segues as like exceptions
Think of the destination view controller as being an exception handler for the unwind segue. The unwind segue propagates up the navigation stack very much like an exception propagates up the call stack. The segue looks for an unwind handler like an exception looks for an exception handler (catch block). It's looking for an unwind handler that is suitable for the type of unwind segue – again, this is like an exception handler for the type of exception being searched for.
Importantly, you can have many view controllers implementing the same unwind handler. The unwind will bubble up like an exception to the first view controller that handles it.
The usual advice for unwind naming is something like unwindToMessageList. This can make sense, but following the "exception handler" metaphor, it can be very useful to name unwind handlers by what they are handling. So, unwindFromEventDetails, unwindFromReport, or unwindFromCloseupImage might be good names describing what is being caught. These handlers can be implemented at multiple possible catch sites. The appropriate handler will be automatically selected using the navigation hierarchy.
There is no correct answer for this question. It depends on taste. I mitigate for readability. Don't be shy to give a long name for your segue identifier; give long and expressive names because Objective-C is a very verbose language leveraging us to write very readable code.
I have searched for an official convention, but I couldn't find any. Here's what Apple has to say:
You assign identifiers to your segues in Interface Builder. An
identifier is a string that your application uses to distinguish one
segue from another. For example, if you have a source view controller
that can segue to two or more different destination view controllers,
you would assign different identifiers to each segue so that the
source view controller’s prepareForSegue:sender: method could tell
them apart and prepare each segue appropriately.
Another quote from Ray Wenderlich's site:
Give the segue a unique Identifier. (It only has to be unique in the
source scene; different scenes can use the same identifier.)
An interesting approach for picking the identifier name (see above link for more):
First write the code that verifies for the segue identifier name BEFORE you even set the name in the interface builder. I'm talking about this code: if ([segue.identifier isEqualToString:#"SegueIdentifierName"])
Build & Run! Don't fill in the identifier name in the Interface Builder yet. You do this because you may have multiple outgoing segues from one view controller and you’ll need to be able to distinguish between them. If nothing happens while you run and trigger the segue you're working on, then your segue name identifier is unique and good to be used. If instead the code performs a segue that you haven't intended, you have a conflict for the sague name identifier.
Fix the conflict - if any.
Fill in the segue identifier in the Interface Builder and test that it does what you want.
I like this because it's like a TDD approach: write a failing test, write some code to pass the failing test, refactor, repeat.
"ViewControllerAToViewControllerB"
For example, if you have a MasterViewController and a DetailViewController, the segue identifier could be:
"MasterToDetail"
Personally I would not use the animation-type in front, if you change the animation, you'll need to go back to the code. However, if you declare the segue identifier as a constant in the source controller, you can more easily change the name at a later stage, without digging through the code.
I generally use the name I gave the controller, without the "ViewController". So RatingViewController would be "Rating" as storyboard. One exception are the unwind segues, I name those starting with "returnTo" ending in the name of the destination ("returnToRating").

Objective-C: Refactoring code - how do I get a pointer to a view instance?

I am not very experienced with OOP so I wanted to share what I am currently doing and ask for some advice about how I should go about a couple of things.
I am currently working on a simple game that uses a 2d grid. Here is a quick overview of my class structure:
'GameBoard'- has an array of the possible cell states for the game, and methods that implement the rules.
'GameView' - has the superclass NSView, and is placed in my window as a custom view. This class is intended to read from the game board and draw the contents of the array by mapping the stares to an enumeration of images in its drawRect: method.
'GameController' - this is an intermediate class intended to initialise the game board and view, and handle the various UI controls. This class has instance variables of the 'GameBoard' and 'GameView' type.
Originally, I was doing nearly everything in the View class, and had it working fine but it started to get hard really to follow, which was my main reason for wanting to spread my code over a new class.
I have created a method in 'GameController' that initialises a new game, with some user defined parameters (removed in the snippet to save space).
-(IBACTION)initialiseGame:(id)sender {
gameBoard = [[GameBoard alloc] init...];
gameView = [[GameView alloc] init...];
}
What I want to do here is pass the game view a pointer to the game board instance so that I can read it's array to draw the current state of the game, something like:
-(void)initWithGameBoard:(GameBoard*)gameBoard;
Is this the right way of going about that, or should I be doing this in a different way?
My next problem with moving to the controller class is that I cannot seem to find out how to do is get a pointer to the instance of GameView that I have placed on the window in IB? Would it be better to not place the view on the window in interface builder, and instead place it on the window programatically in the initialiseGame function? If so how would I go about doing that?
I guess one other question would be, should I just scrap this idea and stick to doing everything in the GameDraw class?
Thank you very much for taking your time to read this, this is probably a very simple question to any experienced object-oriented programmer, but I cannot seem to find the answers specifically anywhere.
There's more than one way to do make this work, but here's how I would do it:
Instantiate the view once in IB. Don't invoke alloc/init yourself.
In your view controller, make an outlet for your view and connect it in Interface Builder. That's how your controller will get access to it. Your view controller will need to be the file owner — probably it already is.
Design the view to be reusable. Give it a -setGameBoard: method for the controller to invoke. Make sure the view can draw something blank when it doesn't have a game board.
Write -initializeGame: like this:
-(IBAction) initialiseGame:(id) sender {
gameBoard = [[GameBoard alloc] init...];
[gameView setGameBoard:gameBoard];
}

iOS: Create copy of UINavigationController

It is possible, to create an exact object copy of a UINavigationController? I have seen examples of copying objects using copyWithZone:, but I am confused as to how I would use this to copy my UINavigationController.
Any help?
UINavigationController doesn't conform to the NSCopying protocol, so you can't use copyWithZone: or copy on it.
If you are looking to have a customised UINavigationController that you can use throughout the app then you should subclass it and then create a new instance of that subclass every time you need a new one, such as when you create a new modal view controller.
EDIT: If you want to keep the view controllers from a previous navigation controller then you can do something like this (use subclassed navigation controller if needed):
UINavigationController *newNavigationController = [[UINavigationController alloc] init];
[newNavigationController setViewControllers:oldNavigationController.viewControllers animated:NO];
This will do a shallow copy of the viewControllers, i.e. you will have references to the original navigation controller's view controllers, not copies. If you want to do a deep copy on the view controllers then that will be far more complicated and will require specific copying code for each view controller. (See here for more info).
You can do this by creating a category (or a subclass), make the category NSCoding compliant, and add the necessary encoding and decoding functions. You then need to determine what properties you want to encode - the types of view controllers it currently has in its array, and perhaps you'll need to make those objects be NSCoding compliant. You can see that this is not going to be a trivial thing to do, but its not impossible. You may find the solution to your problem is best done using some other techniques.
EDIT: If you want to "duplicate" it, what you really need to know is what viewControllers are in the array. So suppose you want to replicate "state", which in some sense is the same as the original answer but less rigorous. Add a category or method to each object and ask to to give you current state as a dictionary. For the navigationController, that might be just the classes of the objects currently on the stack.
For each of these objects on the stack, you get them to give you a dictionary of their state. By state, its means what text is in UITextFields, views etc, anything that that object would need to go from a startup condition and get back to where it is now.
You package this all up - the nav dictionary and array of the state ones. You can save this as a plist. When you want to construct where you were later, the nav controller can tell what objects to create by knowing their class, then as each one is created it can be sent its dictionary and told "get back to where you were". Once done, then push another controller on the stack.

Traversing the ViewController hierarchy properly?

I'm having trouble referencing one view controller from another. The code works but I get warnings which makes me think I'm going about it wrong. I'm trying to reload the data in a tableView whose controller is in a NavigationController.
What's wrong with a message like this:
From the AppDelegate:
[self.tabBarController.selectedViewController.topViewController.tableView reloadData];
Although this works, I get the warning request for member 'topViewController' in something not a structure or union because Xcode doesn't know that the selectedViewController will return a navigationController. So I could do the following:
UINavigationController *myNavigationController = self.tabBarController.selectedViewController;
[myNavigationController.topViewController.tableView reloadData];
But then I get this warning: incompatible Objective-C types initializing 'struct UIViewController *', expected 'struct UINavigationController *'
How far do I have to go with this? The first line works. To get to the "right way" is it gonna take 8 lines of code?
A major code smell here, IMO. You're trying to do action at a (great) distance. It's not exactly clear what you're trying to accomplish, nor why you need to do this action from the app delegate. I have seen some developers treat the app delegate like a giant catch-all global lump of mud, and I think this is an anti-pattern that should be eliminated from iOS development.
Back to your question: you're trying to force a table view controller, inside a tab view controller, to reload its data. I'm assuming this is in response to something happening. Why not have the view controller in charge of that table watching for that event instead of the app delegate? That way, the thing that owns the table view is directly controlling it -- which is the entire point of the MVC pattern. This is a much better approach than having the app delegate drill down through a hierarchy to find a table view... in terms of complexity, readability, and brittleness.
If, for some reason, you can't or won't have that view controller observing for the event directly (hard to fathom why offhand), you could always have the app delegate post an NSNotification and let the view controller in charge of the table register as an observer for it. Not as good as direct observation, but definitely better than your current approach.
You can't use dot-notation unless the compiler knows what type of object you are using it on, and that that object type can receive a message with that name.
You can use dot-notation with a bunch of type-casts (which in this case, is hideously ugly):
[((UITableViewController *) ((UINavigationController *) self.tabBarController.selectedViewController).topViewController).tableView reloadData];
Or you can break it up into discrete steps:
UINavigationController *navController = (UINavigationController *) self.tabBarController.selectedViewController;
UITableViewController *tableViewController = (UITableViewController *) navController.topViewController;
[tableViewController.tableView reloadData];
Note that I'm assuming that your top VC is a sub-class of UITableViewController.
You really shouldn't be accessing the .tableView property externally - you should encapsulate that behaviour with a reloadData method on the View Controller itself. Even if all it does is call reloadData on its .tableView, you should encapsulate it. This will make your code more modular (which makes it easier to understand for you and others), and make it easier to expand on and add complexity to your View Controller down the track.
Without knowing exactly how this app is structured, I would guess that you're probably better off using notifications or observers to get your VC to reload its data. If you have some global event that requires a UI refresh, an NSNotification is a good way to make the UI layer get the message while keeping your code nice and modular.

Cocoa Touch UIViewController Properties and class design

I'm creating a custom ViewController. This VC needs to load some data that is known in the event that creates it and pushes it to the top of the NavigationController that it is going to be part of.
My question is, how should I pass data from the view that handles the custom ViewController's creation into that custom ViewController.
I've thought of four possible options, and I was hoping to get feedback on why each one is good or not for this functionality.
1) Expose public properties in the custom ViewController and set the UI elements in the view based on those properties in - (void) ViewDidLoad.
2) Expose the actual UI elements themselves and set their .text/.image/.whatever attributes as the ViewController is being created.
3) Create a custom constructor for the custom view and pass in the values I need to set up the UI elements
4) Create a custom model that both views have access to, set the data before the CustomView is created/pushed, and access that data in the ViewDidLoad event.
I'm still new to all of this, and I want to make sure that I understand the proper handling of these handoffs of data. It seems like something like this is probably a simple answer, but I'm still a little confused and its probably really important to do this right to avoid memory loss/leaks.
Also, in case anyone cares, I'm using Stanford's CS193p class on iTunes U and Mark/Lamarche's "Beginning iPhone Development" to teach myself cocoa for the iPhone. I'm working on an application with a NavigationController and a couple ViewControllers (Presence 1 if you're familiar with 193p).
Well, I believe there are advantages & disadvantages to each of those methods depending on your requirements...often it will require some combination of approaches. I believe the most common, for me anyway, is to do something like this where you give it enough to get started.
MyViewController *vc = [[MyViewController alloc] init]; // (or initWithNibName:bundle:)
// transfer vc values here
vc.value1 = aValue;
vc.value2 = anotherValue;
[self.navigationController pushViewController:vc animated:YES];
[vc release];
After your view controller is instantiated you have an opportunity to pass objects to it. Say MyViewController is a detail view then you'd give it the object it will be displaying the details for. Or, if it's a table view you can give it the NSArray it will need for display. Then in viewDidLoad or awakeFromNib or awakeFromCoder, or... you can fill out the view...so to speak.
#1 is fine, with or without #3 (these two are not mutually exclusive)
#4 is my preferred solution. For instance, if I had a UserViewController, I would probably also like to have a User object and create it this way:
User *user = [self.users objectAtIndex:someIndex];
UserViewController *uvc = [[[UserViewController alloc] initWithUser:user] autorelease];
#2 is not a good idea. Objects should not access the UI elements of other objects. Much trouble comes from this when you decide to change your UI around (and you will).