I have a ViewController that responds to some touchEvents (touchesBegan, touchesMoved, etc...).
I've found that when I show this controller using presentModalViewController: it works just fine, but I'm trying to add it's View as a subview of another ParentViewController like so:
- (void)viewDidLoad
{
[super viewDidLoad];
//Add SubController
controller = [[SubViewController alloc] initWithNibName:#"SubViewController" bundle:nil];
controller.view.frame = CGRectMake(0, 30, 300, 130);
[view addSubview:controller.view];
[controller release];
}
When I do this, it gets added the parent view but it no longer responds to touch events. Is there a way to fix this?
Also, is there a better way to go about this? I know I probably could have used a View subclass for the child view, but it's supposed to use a Nib and I wasn't sure how to handle that without using a ViewController.
You're correct you should use a UIView subclass.
The easiest way to load it from a nib is to include the subview in your nib.
Just drop a UIView into the view connected to the original view controller.
Then with the view inside selected go to the identity inspector. It's the one that looks like a little ID card.
The very first field is called Custom Class.
Type the name of your UIView subclass here.
If you need a reference to this just create an IBOutlet in your original view controller and hook it up. That way you can set hidden = YES until you need it.
In your UIView subclass you might want to override
- (void)awakeFromNib
This will get called when the nib first unpacks.
for setting up any gesture recognizers, etc.
To load a nib directly into a view :
// Get the views created inside this xib
NSArray *views = [NSBundle mainBundle] loadNibNamed:#"myViewNib" owner:nil];
// There's probably only one in there, lets get it
UIView *myView = [views objectAtIndex:0];
// Do stuff . . .
[[self view] addSubview:myView];
You could try to call becomeFirstResponder in your subview and see whether it receives touchesBegan... It is probably so, but it will also possibly make the superview not receive touchesBegan if you require it...
Related
Bit confused with this one so bear with me...
I have a Navigation-based project which is working fine. I'm trying to create my first custom UIView to make a couple of buttons which I will use in multiple places. One of the buttons needs to push a viewcontroller into the navigation when it's clicked but I'm not sure how to do this.
When I had the button set up within a view controller I was using:
LocationViewController *controller = [[LocationViewController alloc] initWithNibName:#"LocationViewController" bundle:nil];
[self.navigationController pushViewController:controller animated:YES];
[controller release];
but the self.navigation controller won't work now, will it? How do I access the navigation controller of the viewcontroller that this uiview will be added to?
Hope at least some of that makes sense, as I said it's my first go at subclassing the uiview and adding it to multiple pages so I'm a bit lost.
EDIT TO ADD - I have the button click events inside the custom UIView, so that is where I'm trying to change the viewcontroller from. Should I instead wire up the events in whichever viewcontroller I add the view to?
Usually your appDelegate has a UINavigationController property. You can access it in your custom view like this:
UINavigationController *navController = (MyAppDelegate *)[[[UIApplication sharedApplication]
delegate] navigationController];
But more effective way is to make delegate method for your custom view and handle button action in your viewController.
MyCustomView.h
#protocol MyCustomViewDelegate
#interface MyCustomView : UIView {
id<MyCustomViewDelegate> cvDelegate; }
#property(nonatomic, assign) id<MyCustomViewDelegate> cvDelegate;
#protocol MyCustomViewDelegate #optional
-(void)didClickInCustomView:(MyCustomViewDelegate*)view withData:(NSObject*)data;
#end
MyCustomView.m
- (void)myButtonClick:(id)sender
{
[self.cvDelegate didClickInCustomView:self withData:someData];
}
So now you can handle this event in any place where is your custom
view.
Add the button from the interface builder or from the view controller's viewDidLoad using code:
CGRect frame = CGRectMake(0, 0, 24, 24);
UIButton *button = [[UIButton alloc] initWithFrame:frame];
[button addTarget:self action:#selector(handleMyButton:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
Then implement -(void)handleMyButton:(id)sender {}; in your view controller. Or you could instead write -(IBAction)handleMyButton:(id)sender {}; and link method and button using the interface builder.
Then inside the method just paste the block of code you posted above. If you started with the Xcode navigation controller template project it should work.
I think it's cleaner to hide the designated initializer initWithNibName: because it is an implementation detail.
When you say you are subclassing the UIView I don't know exactly what you mean. If you want to add another view controller with a custom view just use the UIViewController template and customize the XIB file, no need to subclass an UIView unless you are really modifying its behaviour, which I guess you are not. The view is a view, and the controller stuff like handling buttons should be in the controller.
The actual controller need to be in the navigation controller stack to be able to push another controller.
Or you can make a new navigation controller instance and push your LocationViewController.
I have a UIViewController that is creating another view controller, and adding its view as a subview:
In the parent UIViewController:
SlateMoreView* subView = [[SlateMoreView alloc] initWithNibName:#"SlateMoreView" bundle:nil];
[self.view addSubview:subView.view];
I then need to call a method from the subview, in the parent view.
I have seen how to do this when I am adding the sub UIViewController using [self.navigationController pushViewController: subView animated: YES], because I can find the parent using this kind of code:
In the sub view UIViewController:
NSArray* viewControllerArray = [self.navigationController viewControllers]
int parentViewControllerIndex = [viewControllerArray count] - 2;
SlateView* slateView = [viewControllerArray objectAtIndex:parentViewControllerIndex];
...and then I can send messages to it. But since I added the sub view manually by using addSubView, I can't do this.
Can anyone think of how I can talk to my parent UIViewController?
Thanks!
UIViews have a superview property which seems to be what you are looking for.
In addition you probably don't want to nest UIViewController's view like that unless you are very deliberately building a custom contain view controller. See http://blog.carbonfive.com/2011/03/09/abusing-uiviewcontrollers/
You might want to consider if your problem can be solved by using NSNotifications. You could post a notification from your subview when an event happens that interested listeners (your superview) need to know about . When the superview receives the notification, it can run whatever code you wish. All the while the subview never needs to know about the superview.
This is one way to make your classes less dependant on each other.
You could also use delegation as another option.
When you add your view as a subview to a view hierarchy, you put it in the responder chain. You can go up the responder chain to reach the view controller as a UIView controlled by a UIViewController has the UIViewController as its nextResponder.
id object = theSubview;
do {
object = [object nextResponder];
} while ( ![object isMemberOfClass:[YourViewController class]] );
// object has the view controller you need.
Here is a related question I found, but it does not answer my question in detail.
[http://stackoverflow.com/questions/3209993/cocoa-touch-can-i-have-multiple-views-per-view-controller-or-specify-bounds-of][1]
I have a UIView class, BallView, which is set to be the default view of the ballViewController. Now, this view has a ball bouncing around according to the accelerometer. I am calling a private function draw every time the accelerometer sends updates.
However, my main question is: I would like to have multiple such balls bouncing around.
Do I have to recreate the view for every class ? But then the File's Owner's IBOutlet view will also have to be connected. And an IBOutlet can point to just one address.
Any other way round this ?
Here is how I'm instantiating the Ball View class in the ballViewController:
[motionManager startAccelerometerUpdatesToQueue:queue withHandler:
^(CMAccelerometerData *accelerometerData, NSError *error){
[(BallView *)self.view setAcceleration:accelerometerData.acceleration];
[(BallView *)self.view performSelectorOnMainThread:#selector(draw) withObject:nil waitUntilDone:NO];
}];
Thus, it means, my question is a bit different from those multi-view tab-bar solutions. Because in those cases only 1 view is shown at a time. I want 4-5 views overlaid on top of each other.
Any help ?
You're right, your view controller can only have a single UIView in its view property. That view though can certainly be used to contain other subviews.
What I would do is have a plain old UIView as your controller's view, and have your BallViews be subviews of that view. Your controller can still control those views, they ust can't all be in its view property.
EDIT: If you're using nib files/Interface Builder, adding a BallView as a subview of your controller's view is pretty easy - just drag a UIView object onto the view, and in the identity inspector you can change the identity of the view to your BallView class.
If you're not using IB, you can also do the same programatically:
// BallViewController.h
#interface BallViewController
{
BallView* ballView;
}
#end
// BallViewController.m
#implementation BallViewController
- (void) loadView
{
...
CGRect frame1 = ...
CGRect frame2 = ...
self.view = [[UIView alloc] initWithFrame:frame1];
ballView = [[BallView alloc] initWithFrame:frame2] retain];
[self.view addSubview:ballView];
...
}
#end
I have a question about UIViewController's subview, I created a UIView subclass MainView, which has the exact size of the screen, I wonder which is a better way of adding MainView, consider the following factors:
1 As MainView has same size as the whole screen, the MainView itself may have subviews, but there is no views at the save level as MainView(ie I don't need to add other subviews to self.view).
2 If I use self.view = mainView, do I put the code in loadView(as the viewDidLoad method means the view(self.view) is already loaded)? I see the loadView method is commented out by default, if I add the code to this method, what other code do I need to put together(e.g. initialize other aspects of the application)?
3 If I add mainView via [self addSubview:mainView], are there actually two off screen buffer? One for self.view, one for mainView, both has same size as the screen and one is layered on top of the other(so it wastes memory)?
Thanks a lot!
I'm not sure I completely understand what you're asking, but I'll try to answer a few of the questions you have.
First of all, if you have multiple UIViews on the screen they are all loaded into memory. You have to do -removeFromSuperview and release them to get the memory back.
You can assign your UIView as the UIViewController's view. For example:
MainView *mainView = [[MainView alloc] initWithFrame:CGRectMake(320.0, 480.0)];
self.view = mainView;
[mainView release]; //since the .view property is a retained property
in that case, you have have the view's initialization code in the -init method. Just redefine it like:
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
//initializations
}
return self;
}
You must implement loadView if you did initialize your view controller with a NIB.
UIViewController takes care of sizing its "main" view appropriately. This is all you need to do:
- (void)loadView
{
UIView* mainView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
self.view = mainView;
}
I'd solve all of this by doing it in a xib! If you create a UIView in your xib, you can then change it's class (when you select the UIView there should be a text field in the Class Identity section of the Identity inspector* - type 'MainView' here!)
Then, create your view controller by calling
myViewController = [[MainViewController alloc] initWithNibName:#"MyNibName" bundle:nil];
That should solve your problems; it's the main subview of your view controller (directly accessable from self.view) and you don't need to worry about memory usage, there's only one view :)
Sam
NB * Click tools -> Identity Inspector. I didn't know it was called this until I had to write this answer!
Yes, the first code-snippet shown above is the "standard" approach, AFAIK, when not using (evil!) NIB files -- i.e. when alloc'ing your view in-code, via loadView.
Note it seems one can also get away with the following, instead of hard-coding the screen-rect size:
UIView *myView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
self.view = myView;
[myView release];
Note you definitely want to do the [myView release] call since, indeed, as pointed out above, self.view (for UIView) is a retained property.
Cheers, -dk
Perhaps the most important thing to do is make sure you have the following:
self.view.userInteractionEnabled = YES;
While it might not be required all of the time, it fixes the issue where self.view is unresponsive. This issue pops up occasionally.
I have got my own custom UIViewController, which contains a UIScrollView with an UIImageView as it's subview. I would like to make the image to auto rotate when device orientation changes, but it doesn't seem to be working...
In the header file, I've got;
#interface MyViewController : UIViewController <UIScrollViewDelegate> {
IBOutlet UIScrollView *containerView;
UIImageView *imageView;
}
These components are initialised in the loadView function as below;
containerView = [[UIScrollView alloc] initWithFrame:frame];
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://..."]];
UIImage *image = [[UIImage alloc] initWithData:data];
imageView = [[UIImageView alloc] initWithImage:image];
[image release];
[containerView addSubview:imageView];
And I have added the following method, assuming that's all I need to make the view auto-rotate...
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
MyViewController loads fine with the image I've specified to grab from the URL, and the shouldAutorotate... function is being called, with the correct UIInterfaceOrientation, when I flip the device too.
However, didRotateFromInterfaceOrientation method do not get called, and the image doesn't seem to rotate itself...
Could someone please point out what I need to add, or what I have done wrong here?
Thanks in advance!
This may not be the right answer for you, because you don't specify the context that the UIViewController's in, but I just found an important gotcha in the Apple documentation that explains the similar problem I'm having.
Tab bar controllers support a portrait
orientation by default and do not
rotate to a landscape orientation
unless all of the root view
controllers support such an
orientation. When a device orientation
change occurs, the tab bar controller
queries its array of view controllers.
If any one of them does not support
the orientation, the tab bar
controller does not change its
orientation.
I've noticed that there are issues when rotating a UIView that's not the first or only view as a direct child of the main window.
So if your UIView is part of a Navigation Controller or a Tab View Controller, you'll also need to override shouldAutoRotateToInterfaceOrientation on the Navigation Controller or Tab View Controller.
Also: using [UIApplication setStatusBarOrientation] helps to work around things if/when you need to do it manually.
To make this kind of thing work in my application, I had to override
- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
[self layoutSubviews];
}
and also layoutSubviews
- (void)layoutSubviews
{
NSLog(#"layoutSubviews called");
...recalc rects etc based on the new self.view.bounds...
}
I'm not sure that this is absolutely required, but it worked for me.
Sometimes, if you add a subview to a view, it's your responsibility to make sure that the methods are passed to the subview; a couple of days ago I wrote a short post about this. For example, if you have a UIViewController and add a UINavigationController as subview, you must add this code to the UIViewController if you want viewWillAppear:animated: to be called when UINavigationController.view appears:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[projectNavigationController viewWillAppear:animated];
}
It might be the case that the willRotateToInterfaceOrientation and didRotateFromInterfaceOrientation method also need to be called by the superview; I am not really sure about this, but give it a try.
This is discussed in Apple Technical Q&A QA1688.
Sometimes if you stack multiple views on top of each other for some reason, the anotherController might not receive rotation event.
[myWindow addSubview:primaryViewController.view];
[myWindow addSubview:anotherController.view];
A lazy way (not a good design) to fix this is only add one subview on window, but initialize multiple controller on the app delegate. Then when you need to switch window, remove the current view and add the view you want
[self.view removeFromSuperview];
AppDelegate *dg = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[[dg window] addSubview:[[dg viewController] view]];
I just came across this having a similar problem. I have a series of view controllers/complex views, that all rotate perfectly and couldn't figure out why the new one I just added on wasn't rotating. After a LOT of trial and error, the reason was that I wasn't calling the init method (it's the standard init method) when allocating the view controller;
e.g. I was doing
m_timerViewController = [TimerViewController alloc];
instead of
m_timerViewController = [[TimerViewController alloc] init];
To expand on jonoogle's post. I had a similar error. My view has a nib and my custom init:
- (id)initWithCategory:(Category *)category inManagedObjectContext:context{
didn't include the call to init the nib.
self = [super initWithNibName:nil bundle:nil];
Adding that line made my view rotate like it is supposed to.
I copied this from this link
And it works for me.... Reason why i have added this here is to make it easy for others to find. It took me many hours to find this fix:
Make a new set of class files of the UIViewController type, go into the .h file of this class and change this line
#implementation MyTabBarController: UIViewController {}
#end
to something like this
#implementation MyTabBarController: UITabBarController{
}
Now go into the nib file and click on the UITabBarController object and go to it's identity tab, and make it Class MyTabBarController.
now in MyTabBarController.m make sure this is in it.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)io {
return YES;
}
You can probably get rid of everything else in there if you want.
just do this if you what to rotate from landscape to portrait!
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}