I am designing an iOS app with 10 main UIViewControllers in it. Each representing a different section of the app. Its basically for a company and shows info about the company.
One of the things I am doing on bottom section of the app (in all the different view controllers) is displaying a UIView which contains a map. This map shows a certain location.
Now it works, but the problem I have is that I have 10 copies of the same code and 10 copies of the same UIView.
Is there anyway I could make a small view controller with one class attached to it that would handle the map and then just create an instance of the view controller in all my 10 view controllers in my app?
I hope my question makes sense. Basically I want to know how I can go about reusing ONE UIView in all 10 of my ViewControllers. So I can just call it or something and it appears.
Update - this is basically what I am trying to achieve
Thanks, Dan.
View controllers can contain other view controllers. You can either use a container view in a storyboard or setup the relationship programmatically (see: Creating Custom Container View Controllers).
The storyboard container view is easiest, but the programmatic solution isn't too bad.
- (void)displayContentController:(UIViewController *)content
{
[self addChildViewController:content];
content.view.frame = [self frameForContentController];
// NOTE: You could also add it to any subview of self.view.
[self.view addSubview:content.view];
[content didMoveToParentViewController:self];
}
- (CGRect)frameForContentController
{
return CGRectMake(…);
}
- (void)viewDidLoad
{
…
MyMapViewController *mapViewController = …;
[self displayContentController:mapViewController];
…
}
- (void)dismissContentController:(UIViewController *)content
{
[content willMoveToParentViewController:nil];
[content.view removeFromSuperview];
[content removeFromParentViewController];
}
Final Note: Have each parent view create its own instance of the map view controller. Resist the temptation to reuse an instance of the map view controller between parents.
Update to address questions
So lets say I had 2 of the same view controllers open at once and they both were displaying the same imported viewcontroller then it wouldn't work right?
You can't do this. An instance of a view controller can only have 1 parent view controller. Create separate instances for each use.
So if I create different instances, I can reuse the same view lets say 5 times in one view?
Yes, if you create different instances, you can put as many as you need on a view.
Let me be clear, an instance is a distinct memory location created using a constructor.
MyMapViewController *mapViewController1 = [[MyMapViewController alloc] initWithNibName:#"MyMapViewController" bundle:nil];
MyMapViewController *mapViewController2 = [[MyMapViewController alloc] initWithNibName:#"MyMapViewController" bundle:nil];
or
MyMapViewController *mapViewController1 = [self.storyboard instantiateViewControllerWithIdentifier:#"MapViewController"];
MyMapViewController *mapViewController2 = [self.storyboard instantiateViewControllerWithIdentifier:#"MapViewController"];
Updated to demonstrate dismissing a container view controller.
Here is a method for a child view controller, so it can use to dismiss itself.
- (void)dismissFromParentViewController
{
[self willMoveToParentViewController:nil];
[self.view removeFromSuperview];
[self removeFromParentViewController];
}
Please try below method:
Create "map controller" super class with inherit to UIViewController and define your need common method and variables.
Inherit your 10 child class into "map controller" super class. And connect common IBOutlets and IBActions to super class.
You can access common methods and variable to super class from child class(10 view controller child class).
Please refer below code
#interface mapController : UIViewController
{
NSString *mapControllerVariables;
}
-(IBAction)mapControllerActions:(id)sender;
#end
#interface yourChileView : mapController
{
}
#end
Related
Hi i have been working on an iOS app.What i am doing is navigating among diffrent view controllers. But the problem is i want finish the current view controller from emoery and then move to the next view controller.
I am using `[self.view removeFromSuperview]; for finishing the cureent view & using
self.loginView = [self.storyboard instantiateViewControllerWithIdentifier:#"LOGIN"];
[self presentViewController:self.loginView animated:NO completion:nil];
for moving to next view controller but the thing is i am not able to remove it from memory.
Please tell me how can i do it?
Thanks in advance.
`
It's better to create a container view controller which manages your view controllers. For example, in viewDidLoad: of container controller you add current controller:
[self addChildViewController:self.currentViewController];
[self.currentViewController didMoveToParentViewController:self];
[self.view addSubView:self.currentViewController.view];
//here set up currentViewController view's frame or constraints if needed
When you need to open login controller, do this:
[self addChildViewController:loginViewController];
[self.loginViewController didMoveToParentViewController:self];
[self.view addSubView:loginViewController.view];
//here set up loginViewController view's frame or constraints if needed
//then remove current view controller
[self.currentViewController willMoveToParentViewController:nil];
[self.currentViewController removeFromParentViewController];
[self.currentViewController.view removeFromSuperview];
Remove from superview will remove it from the current view, but OS won't remove it until he needs to (this is topic for more explanation, let's say it won't remove it asap).
If you want something deleted just call it nil:
self.view = nil;
This will make the pointer to nil, so view won't be there any more. (the view really will be somewhere but you won't have access to it)
I am revising your code
self.loginView = [self.storyboard instantiateViewControllerWithIdentifier:#"LOGIN"];
[self presentViewController:self.loginView animated:NO completion:nil];
What you are doing here is presenting your login viewcontroller.
self: This is the instance of the viewcontroller you are currently working on. So how could you remove self from memory. (Not Possible)
You can approach alternate ways.
For example: 1. Changing root view controller
Pop to root view controller and then Push Login View controller.
If you try to remove not only UIView but the whole UIViewController from a navigation controller stack use this snippet
NSMutableArray *stack = [[NSMutableArray alloc] initWithArray: self.navigationController.viewControllers];
[stack removeObject:yourController];
self.navigationController.viewControllers = stack;
Be aware of using this only when you've already pass to the next controller view.
UPD:
Oh, now I see what you are trying to do. And I can't figure out, why you're trying to step your controllers this way (modally). I think you should use UINavigationController with navigation segues defined directly from your storyboard. Look at this article where apple purely explains what navigation is. That article is about modal views
This is puzzling me.
The context
The original tutorial I'm following.
Where the segue is added to the Main View via a custom segue:
- (void) perform {
MainViewController *source = (MainViewController *)self.sourceViewController;
UIViewController *destination = (UIViewController *) self.destinationViewController;
for(UIView *view in source.main.subviews){
[view removeFromSuperview];
}
source.currentViewController = destination;
destination.view.frame = CGRectMake(0, 0, source.main.frame.size.width, source.main.frame.size.height);
[source.main addSubview:destination.view];
}
The TextField is connected as delegate in the child View Controller. All things being equal I get the app crashed without any message.
The workaround
In the Main View Controller, in -(void)prepareForSegue: I've added [segue.destinationViewController setDelegate:self]; in the meantime I've added a property in the child View Controller id<UITextFieldDelegate> delegate and modified the textfield delegate as self.delegate.
This works, but the trouble is that I've to set the delegated methods in Main View Controller which is not quite efficient as I have more View Controllers to add.
The Objective
How do I set each View Controller to be the delegate for itself without crashing?
The immediate cause of your error is that the view controller that your views belong to is being deallocated. The fact that your views are on screen while their view controller is deallocated highlights a fundamental flaw in the approach of taking views off one view controller and adding them to another. View controller containment is the correct way to solve an issue like this.
Changing the currentViewController property to strong will fix the memory management issue you're seeing, but it's just a bandaid. Your currentViewController will still be missing rotation methods, appearance and disappearance methods, layout methods, and so forth. View controller containment ensures these methods get called for the view controller whose views are on screen.
Here is an altered version of your project that illustrates how to use view controller containment. I think that will be a better solution than manually removing and adding subviews of the view controllers themselves. See the Apple docs for more info on custom view controller containers.
At first, let's see crash report. Please, do the following:
1. Add Exception Breakpoint
2. Edit it as in the picture
You should create a custom class for the destinationViewController wich will implement UITextFieldDelegate
#interface DestinationViewController <UITextFieldDelegate>
#end
And from storyboard add the class to UIViewController that has TextField
And make the connections for elements and TextField delegate.
Implement delegate methods.
You will not need the implementation of prepareForSegue: anymore. You will have two different classes with different elements. Only if you need to pass something from source to destination then you use prepareForSegue:
Hope you'll understand
I am writing an iOS7 app in Xcode 5 with storyboards. In a part of the app, I need three screens that shared the same viewController class. These screens are UIViewControllers. I use a UISegmentControl to go from screen to screen based on conditions. I disabled the control if the user had not completed certain steps.
I used a BOOL value check if certain steps had been completed and set its value to YES / NO.
The problem is when I want to go back to the last screen - I am getting a new instance of my viewController class. This has two problems:
Memory grows each time the user goes between the two views
BOOL value and all other properties are nil when new instance loads.
In my segment control this is how I get to the views:
-(void)segmentcontrol:(UISegmentedControl *)segment
{
if (segment.selectedSegmentIndex == 0)
{
self.viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"stepOne"];
[self presentViewController:self.viewController animated:NO completion:nil];
}
else if (segment.selectedSegmentIndex == 1 ){
self.viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"stepTwo"];
[self presentViewController:self.viewController animated:NO completion:nil];
}else {
}
}
This viewController is a subclass of my BaseViewController - which I used for UI elements that is constant across all screens.
What I want to do is return the same instance of the viewController class when I change the segment control to a another view, using the same class.
Is this at all possible?
Not clear why you're using presentViewController:animated:completion: but it looks like you're doing things in the wrong way.
What you want to be doing is creating a container controller. So, the view controller which hosts the segmented control creates a number of view controller instances and adds them as child view controllers. Now, when the segments are selected you get the child at the selected index, remove the old view controllers view from its superview and add the new view controllers view as a subview.
You don't need to do it like that, but it will probably be cleanest. Your memory grows currently because you use instantiateViewControllerWithIdentifier:. All you really need to do is to keep an array of view controllers and reuse instead of recreating. That said, continually presenting view controllers is not wise.
I'm making a newsstand app and I need multiple components in a single view. I have a UIViewController that displays and manages a pdf (zooming, scrolling,etc...) but I don't want it to be full screen instead I want it to be like an UIView inside another UIViewController which has a menu on top, page numbering on the bottom and other tools. Both are subclass of UIViewController... I already have both working separately. I just want the pdf viewer inside the other. Is it possible? here's some code of what I want (lets say is inside the viewDidLoad)... but obviously doesn't work. Any help is appreciated. It also must be IOS 5.1.1 compatible and above
NSString *path = [[NSBundle mainBundle] pathForResource:#"myPdf" ofType:#"pdf"];
PDFViewController *page = [[PDFViewController alloc] initWithPDFAtPath:path];
//pdfView is a UIView of size 600x600 right in the middle
[self setPdfView:[page view]]; //this doest work... but it's basically what I want instead of presenting a viewController
//[self presentViewController:page animated:YES completion:NULL];
I'm also aware of this, but I don't understand... if this is the way I would like some guidance:
addChildViewController:
willMoveToParentViewController:
didMoveToParentViewController:
As of iOS 5 you can do View Controller Containment which lets you add child view controllers into a parent view controller. The child view controllers can manage all the logic like they normally would but the view of the child controller can match any frame/position you like.
To add the controller you can do
[self addChildViewController:childViewController];
childViewController.view.frame = [self frameForChildController];
[self.view addSubview:childViewController.view];
[childViewController didMoveToParentViewController:self];
You can learn more about it in the View Controller Documentation
iOS 6 introduced the Embed Segue, allowing custom container controllers to be used in Storyboards. Is there anyway to duplicate this for iOS 5?
The challenge here is that the child view controller's view is often to be added as a subview of some container view of the parent view controller. Since you can't have segues from random UIView controls, that defies creating segues from a UIView container view to the child's scene. Thus, you just have to write the code yourself.
Fortunately, it's just those four lines of code referenced in Adding a Child Controller from the View Controller Programming Guide. Personally, I'd might even modify that code slightly, having the following method defined in my view controller:
- (void) displayChildController:(UIViewController*)childController
inContainerView:(UIView *)containerView
{
[self addChildViewController:childController]; // 1
childController.view.frame = containerView.bounds; // 2
[containerView addSubview:childController.view];
[childController didMoveToParentViewController:self]; // 3
}
I have, though, done custom segues for changing the active child controller from one scene to the next, but it essentially just a variation of the code listed later in the above referenced document. But that's not an embed segue question, so that's not relevant here
I duplicated the functionality by subclassing UIStoryboardSegue.
In Interface Builder, I create a custom segue and set its' class to my subclass (QCEmbedSegue). In my parent view controller's viewDidLoad, I call performSegueWithIdentifier:sender.
QCEmbedSegue simply overrides perform:
- (void)perform
{
[self.sourceViewController addChildViewController:self.destinationViewController];
[[self.sourceViewController view] addSubview:[self.destinationViewController view]];
[self.destinationViewController didMoveToParentViewController:self.sourceViewController];
}
http://www.quentamia.com/blog/embed-segue-in-ios-5/