Design pattern for organizing UIViewController transitions - ios

I'm trying to come up with a good way to organize transitioning between UIViewControllers that removes the logic from the controllers themselves. I came up with a pattern based on the Pro ObjC Design Patterns book that I liked, but I'd like to know how to improve it.
The pattern in a nutshell. Forgive the semi-pseudo code.
I subclassed the UINavigation Controller and created a method that all the View Controllers call when they need to transition.
-(void)requestViewChangeByObject:(id)object withData:(object*)someData{
if ([object isKindOfClass:[ViewController1 class]]) {
[self showViewController2Animated:YES withSomeData:nil];
}
if ([object isKindOfClass:[ViewController2 class]]) {
[self showViewController3Animated:YES withSomeData:someData];
}
if...
}
Then I just defined methods for each controller. Some just instantiate and push the controller and some set properties or pass information in.
-(void)showViewController2Animated:(BOOL)animated withSomeData:(object*)someDataVariable{
viewController2 *VC2 = [viewController2 defaultViewController];
[self pushViewController:VC2 animated:animated];
}
-(void)showViewController3Animated:(BOOL)animated withSomeData:(object*)someDataVariable{
viewController3 *VC3 = [ViewController3 defaultViewController];
VC3.someData = someDataVariable
[self pushViewController:VC3 animated:animated];
}
The reason I'm doing it this way is because it makes the application much more flexible in terms of changing around controller order and adding/removing controllers as the design and requirements change. We also tend to re-use apps and this makes it easier to reskin and reorganize to build a new application.
The main problem I have is the more complicated the application gets the bigger that method with all the if statements is going to get. It might get confusing if there's more logic involved than just push viewController2 if the request comes from viewController3. I'd really like to know how I can improve this so that it's more flexible and less likely to cause confusion and errors.
Second, it's not very flexible when you add passing data around to the problem. I ended up taking the parameter out that accepted a data object and just created an separate singleton manager object that handles saving/getting the data that I needed, which I understand is not good design. However, when I tried passing the data back and forth between controllers like Apple suggests(ie setting properties on the controller as in the above code) it just became a confused mess and I feel like there's a better way to do this.
So any suggestions on this would be appreciated thanks.

Related

Can the same ViewController.view be added as subview to different views as a Singleton?

My app has a search view(search bar) which is used all over the app. I don't want to create duplicated code so I created a view controller called MySearchViewController to handle the search job, then I created a singleton object in AppDelegate. In every view controller, I added my search view like this:
- (void)viewDidLoad
{
MySearchViewController* search = [AppDelegate searchViewController];
[self.view addSubView:search.view];
}
My questions, Is it a good way? It's a singleton so it can be added to many views. Do I need to remove the view from last view before adding to current view?
Understand that you are mixing some concepts that are not necessarily related: avoid duplicated code and Singletons.
Wikipedia says this about singletons:
In software engineering, the singleton pattern is a design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. The concept is sometimes generalized to systems that operate more efficiently when only one object exists, or that restrict the instantiation to a certain number of objects. The term comes from the mathematical concept of a singleton.
The most important characteristic of a singleton (in my humble opinion) is that the object is instantiated only once and every single place in your application will use the same instance. Well, to use your search feature everywhere and avoid duplicated code you don't need the search view to be instantiated only once, maybe the data that comes with it, but not the view itself.
Two better ways of achieving this:
1 - You can create a ViewController with your search and just embed this on the other views using a Container View, you can use blocks or a delegate protocol to communicate between your controller and the view that is embedding it.
2 - You can create a Parent class of the ViewController that will include the search bar, like a SearchViewController and all the other viewControllers that needs the same feature will inherit from it.
The singleton could be useful if you are planing to share the same search data and text between all the ViewControllers of the application, but it would be a singleton only with these information, the UISearchBar and all other view elements should not be part of the singleton.
Ideally, you should instantiate a fresh instance of MySearchViewController every time when you want to add it to another view to avoid problems.
Do I need to remove the view from last view before adding to current view?
Its not required to remove it from previous super view because whenever you add this singleton MySearchViewController's view to some other view, it will automatically gets removed from last super view and now its super view is your new view where you have added it.
If you want to add a view from a different view controller, your view controller has to be that view controller's parent view controller:
- (void)viewDidLoad
{
MySearchViewController* search = [AppDelegate searchViewController];
[self addChildViewController:search];
[self.view addSubView:search.view];
}
also, make sure that when the search.view is added, it is already initialised.
Why you do not use NSObject class ?, i do not know your requirement , but if you want to store latest updated value in whole project(in execution) then you should use the singleton, but if you do not want to store value (i mean one result for whole project) then you should use NSObject derived Class. advantage is singleton consumes memory so memory will be wasted. NSObject class will be reusable and only allocated when it is required and then ARC will take care of all things. If you want to know how to create NSObject and use of it then you can give me reply.
Here is some code to load a XIB as part of a custom object with the object gets initialized.
Why are you not creating custom search component for search?
you can use this component all over the app.
also this is not creating duplicat code.
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[[[NSBundle mainBundle] loadNibNamed:#"SearchView" owner:self options:nil] objectAtIndex:0];
[self addSubview:self.view];
self.frame = self.view.frame;
}
return self;
}
Please check below code. Hope this is work for you.
- (void)viewDidLoad
{
if ([self.view viewWithTag:123456]) {
[[self.view viewWithTag:123456] removeFromSuperview];
}
MySearchViewController* search = [AppDelegate searchViewController];
search.view.tag = 123456; // give a any random tag to view
[self.view addSubView:search.view];
[self addChildViewController:search];
}
Please make sure given tag is not assign to other object except search.view in self.view.
Thanks

Method Swizzling to keep a cleaner Class implementation

I'm in the situation where one of the viewControllers of my app is getting pretty big, specially since I've added a "Tutorial state" which adds a different implementation for many methods of this class that I control by checking
_tutorialEnabled?
So, my question is if this is a good use case for method swizzling, I could have this different implementations of these methods in a separate category and swizzle them when required, it might help me reduce the amount of code of the default implementation. Any comments or suggestions of other techniques are appreciated.
No, this is not what method swizzling was designed for.
Personally I would create a subclass of the view controller that manages tutorial related stuff. Then, depending on whether or not the tutorial is enabled, you instantiate either the tutorial controller or its superclass. This is what polymorphism was designed for: to avoid endless if/else/switches.
Why don't you subclass? Create a tutorial subclass of your view controller with all the needed logic. Present the tutorial in the real view controller's -viewDidAppear: using a full screen modal without animation. When the tutorial is over dismiss the model without animation.
if _tutorialEnabled != nil && _tutorialEnabled {
tutorialViewController = …
tutorialViewController.modalPresentationStyle = .FullScreen
presentViewController(tutorialViewController, animated: NO) {}
}
No, I wouldn't use method swizzling for this. It's a bit like using a sledgehammer to knock in a thumbtack.
Unlike others I also would not subclass a view controller, maintaining understandable flow around view lifecycle events is really important when you want to add other functionality later.
Instead I would use the strategy pattern for this. In your init you could do something like this:
if (tutorialEnabled) {
self.behaviour = [TutorialBehaviour new];
} else {
self.behaviour = [NormalBehaviour new];
}
Then when you need to do something that changes you just call a method on your behaviour eg.
- (void)viewDidLoad
{
...
[self.behaviour load]
...
}

Identify which Storyboard is active

I need to work out how to identify what storyboard is active at any given time. I have a specialised nativation in which I need to identify the storyboard (UIView) then change things programmatically depending on what the user presses.
All storyboards have Identifiers.
in the viewDidLoad of the root view I have the following.
- (void)viewDidLoad
self.topViewController =
[storyboard instantiateViewControllerWithIdentifier:#"View1"];
{
What I would like to do is identify which storyboard the user is on and depending on the press do the following sudo-code
- (void)viewDidLoad
if (storyboard.name != RootView)
self.topViewController =
[storyboard instantiateViewControllerWithIdentifier:#"View1"];
{
else if (storyboard.name = View2){
self.topViewController =
[storyboard instantiateViewControllerWithIdentifier:#"View2"];
}
etc....
I have step through the code and seen the StoryboardID however it's which I'm pretty sure your not meant to use....
Thanks in advance
Jeremy
UPDATE: Explanation to Navigation
I'm trying to implement the ECSlideViewController, but It's doing my head in. Effectively adding in the slide to the right function to reveal more options. SO, this thinking was going to be easy turned out icky. I have the master UIViewController<title:HomeView> I then have 4 buttons on the screen which segueway to other UIViewControllers<View1>, UIViewController<View2> etc.
In order to produce the effect on View1,View2,View3,View4 I need to bring the class (ECSlideViewController as per the example) into the UIViewController<HomeView>. However If I change the below code to represent this...
self.topViewController =
[storyboard instantiateViewControllerWithIdentifier:#"HomeView"];
It crashes because it calls itself. Not good, circular coding is a no no.
but if I set it to what was originally there
self.topViewController =
[storyboard instantiateViewControllerWithIdentifier:#"FirstTop"];
( btw firstTop is the title of the view used with the example)
It works but then disregards the UIViewController<HomeView>
This is why I asked if there was a way to identify the self.title of the UIViewController(said storyboard...my bad) as I was going to put conditional logic around it in order to not put the code in if it's on the UIViewController<HomeView>.
It really is hard to explain unless you download the ECSlideViewController and start playing with it. But effectively I just want to test the self.title.....I think...
The other idea was to bring the logic into the UIViewControllers of the four and get it to work there...but It freaks out passing nil as it's expecting an identifier...
Hope this makes sense....
J.
Okay Guys,
Totally ditched ECSlideViewController. I found a few articles explaining that it had issues when you had multiple UiViewControllers not passing data correctly. I ended up using Andrews suggestion. http://www.youtube.com/feed/UCJA_puohXgnze8gPaerTeig It worked for easier for me.
Although I take note of what the design guidelines Apple have an this is usually a no no, but I'm hoping that they won't mind.
Thanks everyone again!
J.
I'm not sure if this helps, but it sounds like you will have to compare instances to get the results you are looking for. I haven't tried this, but I would create properties of each storyboard in your app delegate, then reference the app delegate in your view controller to compare. This might not be the best coding practices, but I'll leave that into your hands.
Something like (untested code):
- (void)testStoryBoard //After awake from nib or viewDidLoad
{
NXAppDelegate *appDelegate = (NXAppDelegate *)[[UIApplication sharedApplication] delegate];
if ([self.storyBoard isEqual:appDelegate.view1StoryBoard])
NSLog(#"View1 Storyboard");
else
NSLog(#"View 2 Storyboard");
}

Segue destination types & how to find them in code

Can anyone tell me how I can phrase an if () statement to discover if a segue's destination view controller will appear in the Detail Split or in the Master Split?
I want to put the if() statement inside my prepareForSegue:sender: methods.
EDIT
All my detail views that are relevant to this question (at the moment) conform to a protocol and I am currently performing introspection on the destination controller using:
if ([segue.destinationViewController conformsToProtocol:#protocol(myProtocol)])...
I can see that this would not work if I wanted:
To be able to show the same class in either Master or Detail of the splitView from time to time, and at the same time...
I only want the if() statement to be true when the view is to be presented in the detail split.
Things like segue.destinationViewController.navigationController == ... don't appear to be any use either.
I was hoping that since we need to set "Master Split" or "Detail Split" when we set the segue up... there would be a way to access that information less circuitously.
SECOND EDIT:
The way I have this set up with using introspection does "work". It just doesn't seem very "Object Oriented". I don't think I should be querying the View Controller at all for this information, I can't see why the VC should know anything about which side of the splitView it will be displayed. Surely the object that should hold onto this information is the Segue and, as I say, it appears this is being "set" in the storyboard when we select "Detail" or "Master" split.
Maybe it isn't a property of anything, but I can't see how to get at it.
I suppose I could query the destinationViewController in its viewWillAppear to discover which NavigationController it is in after it appears on screen but, again, it seems a bit "hacky".
There is probably a better more abstract and reusable way to do this that I'm not aware of, but here is a suggestion that could help in your specific project that requires just a bit of special knowledge of your specific project.
If you use introspection in your prepare for segue, you can check to see if methods exist by using the responds to approach.
So for example, in typical implementations of a splitview controller (note - not all) the detail view will implement the methods to handle rotation. So if this is true in your project, you could do something like this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.destinationViewController respondsToSelector:#selector(splitViewController:shouldHideViewController:inOrientation:)]) {
//do something
}
}
You could use this same approach based upon something that was unique but constant in your project related to either the master or detail view.
hope that helps,
be well
My experience is a little limited, but most times I've seen prepareForSegue used, the if() block checks segue.identifier to do anything that needs to be done specifically to handle building the new page. If you set the identifier for all your segues, you could just have code to handle each segue from that controller, and change what the code is depending on if that segue goes to a masterViewController or a detailViewController. Not really a well automated way, but it'll get the job done.
EDIT: oh geez, that wording is kinda confusing. If you want me to I can put a code example up, but it'll have to wait until Monday, as I don't have access to a Mac until then.
The talk of classes and protocols gave me another idea, but again, not sure if it will work - I wanted to test it before posting, but I'm not going to have the time to test anytime soon.
I think you should be able to create 2 new classes, UIMasterViewController and UIDetailViewController, that are subclasses of just UIViewController. Then, for each of your actual screens, instead of making them subclasses of UIViewController directly, make them either a UIDetailViewController or UIMasterViewController. Then, in your prepareForSegue,
if ([segue.destinationViewController isKindOfClass:UIMasterViewController])
{
//do master view specific stuff
}
else if ([segue.destinationViewController isKindOfClass:UIDetailViewController])
{
//do detail view stuff here
}
This should be a pretty dependable way to tell where your segue is sending you, as long as you can set up the custom view controller classes right. This still won't solve the first issue noted in the question
"To be able to show the same class in either Master or Detail of the
splitView from time to time, and at the same time..."
This could be overcome by making 2 copies of all of the views you want to be able to show as either or both, then make one a UIMasterViewController and the other a UIDetailViewController - copy-paste should be good for most of the rest.
Let me know if this works - I'm not exactly sure how to set up the controllers off the top of my head, but I'm pretty sure it could be done. If it can, I can see this being a very useful thing.

Design pattern for sharing a network data model between ViewControllers

I am currently developing an iPad application. For business reasons there wont be any data persistence on the device. The data will be accessed from a back-end server as needed using NSURLConnection. I have developed a 'model' object which does all the network access. The UI has a split view controller with a table view controller inside a UINavigationControlller as the root controller. User will drill-down on the table view controller to eventually load the detail view controller. The table Viewcontrollers are passing a reference to the model object when they are being loaded into the UINavigationController so that they can dynamically generate parts of the Table View Cell from the model. In order to be responsive, each Table View controller sets itself as the delegate of the Model object in the view will appear and when the cell is selected, queries the model object, which in turn updates the UI via a delegate method.
My question is where is the best place to set and unset the delegate of the data model?. Currently I am setting the delegate in the ViewWillAppear and setting it to nil immediately after navigation Controller:pushViewController:Animated.
// Setting the delegate
- (void)viewWillAppear:(BOOL)animated {
// set ourself as the delegate
[[self dataModel] setDelegate:self];
// Get the count of studies
[[self dataModel]GetListOfDiagnosticStudyResultsForID:[[self currentPatient]patientID]];
}
// setting delegate to nil
DiagnosticStudiesViewController *selectedVC = [[DiagnosticStudiesViewController alloc] init];
selectedVC.dataModel = self.dataModel;
[[self dataModel]setDelegate:nil];
[[self navigationController]pushViewController:selectedVC animated:YES];
Is this appropriate? Could you think of any issues with this pattern. The program is very responsive and I do not see any issues in the instruments. Is there a better way to do this?.
Sorry that this question is long winded.
I think this is an okay approach but there are a couple of considerations to be made:
You're sharing the dataModel with 2 views so you may have to update the view when you return to the DiagnosticsStudiesViewController's parent (self in your code) depending on how dataModel data is displayed.
This might get hairy in the future if you need to thread your code. In that case you might have to make a copy of the dataModel to pass to DiagnosticsStudiesViewController or handle edits to dataModel in a thread-safe manner.
You'll obviously require a network connection for both view controllers to work so you've made a workflow decision with your two view controllers by pulling dataModel from the server. In the future it may be hard to uncouple these view controllers.
If it works for your case and the decision has been made to not persist I think you'll be fine.

Resources