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
Related
I have a UIView subclass -
#interface DatePickerPopup : UIView
UIToolbar *toolbar;
UIDatePicker *datePicker;
#end
#implementation
- (id)initWithFrame:(CGRect)frame
{
NSArray *xib =
[[NSBundle mainBundle]
loadNibNamed:#"DatePickerPopup"
owner:self
options:nil];
self = [xib objectAtIndex:0];
if (self) {
}
return self;
}
#end
and the nib looks like -
In my UIViewController containing the DatePickerPopup (datePopup):
- (void)viewDidLoad
{
datePopup = [[DatePickerPopup alloc] initWithRect:CGRectZero];
CGRect newFrame = datePopup.frame;
newFrame.y = 200.0f; //lets say this aligns it to the bottom in portrait
datePopup.frame = newFrame;
// Normally happens when accessory button pressed but for brevity...
[self.view.superview addSubview:datePopup];
}
- (void)willAnimateRotationToInterfaceOrientation:
(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration
{
CGRect screen = [[UIScreen mainScreen] bounds];
if (toInterfaceOrientation == UIInterfaceOrientationPortrait ||
toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
{
self.datePopup.frame =
CGRectMake(0.0f, newHeightPortrait, screen.size.width, 260.0f);
}
else
{
self.datePopup.frame =
CGRectMake(0.0f, newHeightLandscape, screen.size.width, 260.0f);
}
}
However, this gets stretched out for some reason when the orientation changes the view gets stretched to the height of the screen bounds - the navigation bar...
after viewDidLoad
after willAutorotate...
Since your view controller appears to be managed by a navigation controller, calling [self.view.superview addSubview:datePopup]; adds your popup as a subview of a UIViewControllerWrapperView, which is one of the private classes UIKit uses to implement the functionality of UINavigationController. Messing with UIKit's private view hierarchy is always risky. In this case, based on the behavior you're seeing, it seems likely that UIKit expects any subview of UIViewControllerWrapperView to be a view controller's view, so it resizes your popup accordingly.
I think the safest way to resolve this is to have your view controller's view be a wrapper that contains your tableView and, when necessary, your popup view. Unfortunately using a wrapper view means that the view controller can't be a UITableViewController. You'll have to change the superclass to UIViewController, set a custom tableView property, and manually adopt the UITableViewDataSource and UITableViewDelegate protocols.
Note: You might be tempted to add your popover as a subview of your window, but I'm not recommending that because UIWindow only autorotates its topmost subview corresponding to a view controller. This means that if you add your popover to your window, it won't autorotate.
EDIT: BTW, by reassigning self = [xib objectAtIndex:0]; in initWithFrame:, you're leaking the object that was originally alloc'd. If you're going to reassign self in this way, you should release the existing object first.
Add the
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
method in the viewController class and return YES. If this method returns YES, only then will the device support landscape orientation. Try out this extra code and see if it helps...
You can set the frame size for landscape in this method itself instead of the current method. PS: I just saw you've used a UIView instead of controller...you might want to change to controller.
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...
I am using the iPhone toolchain on Linux and so I have no Interface Builder. So how could I layout my view in my ViewController subclass? For example, I want a UITextView in the middle of the screen? Should I do this in the loadView or viewDidLoad. Do I also have to set the view for the ViewController subclass to itself?
It is not an easy job to layout all the view using code. Here are some code:
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake (100, 100, 100, 100)];
[self.view addSubview:textView];
The frame is the place (the first and second argument is x and y coordinator) and the size (the third and fourth argument is width and height of the text view).
Using this way, you can add any view into your class. Some of the view is built in and you don't have to draw yourself, some of them is not, and you need to subclass UIView and override drawRect.
You should do this in viewDidLoad when your main view controller is finished loading
I've written an open source project that does exactly this:
https://github.com/charlesmchen/WeViews
Here's another project that you might find useful:
http://code.google.com/p/layoutmanagers/
I usually build the entire view hierarchy in the loadView method and perform additional setup in the viewDidLoad, for example to set up the subviews content to reflect the data associated to the view controller. The important thing is to set the view controller view outlet in the loadView method.
#synthesize label; // #property(nonatomic,retain) UILabel *label declared in the interface.
-(void)loadView {
// Origin's y is 20 to take the status bar into account, height is 460 for the very same reason.
UIView *aView = [[UIView alloc]initWithFrame:CGRectMake(0,20,320,460)];
[aView setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[aView setAutoresizeSubviews:YES];
// The 150x50 label will appear in the middle of the view.
UILabel *aLabel = [[UILabel alloc]initWithFrame:CGRectMake((320-150)/2,(460-50)/250,150,50)];
// Label will maintain the distance from the bottom and right margin upon rotation.
[aLabel setAutoresizingMask:UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleLeftMargin];
// Add the label to the view hiearchy.
[self setLabel:aLabel];
[aView addSubview:aLabel];
// Set aView outlet to be the outlet for this view controller. This is critical.
[self setView:aView];
// Cleanup.
[aLabel release];
[aView release];
}
-(void)viewDidLoad {
// Additional and conditional setup.
// labelText is an istance variable that hold the current text for the label. This way, if you
// change the label text at runtime, you will be able to restore its value if the view has been
// unloaded because of a memory warning.
NSString *text = [self labelText];
[label setText:text];
}
-(void)viewDidUnload {
// The superclass implementation will release the view outlet.
[super viewDidUnload];
// Set the label to nil.
[self setLabel:nil];
}
The biggest difficulty is probably understanding how IB settings map to UIView variables and methods, for example the autoresizing mask. Apple's UIView and UIViewController class references are full of useful informations.
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);
}