iPad orientation and xib files - ipad

I am using one xib file as my first screen in my iPad app.In that xib file I am using one image view in background and one button,if I click on that button then it will go to another page.My application supports both orientation.Now the problem is , suppose I click on that button and enter to the second page and then change the orientation of the device then go back to the main page, now in main page button's frame is changed. so I am not able to understand where to click to go to second page for second time.
Please help me to get rid of this problem.

I found a much better way that works independent of the nav controller. Right now, I have this working when embedded in a nav controller, and when NOT embedded (although I'm not using the nav controller right now, so there may be some bug I've not seen - e.g. the PUSH transition animation might go funny, or something)
Two NIBs, using Apple's naming convention. I suspect that in iOS 6 or 7, Apple might add this as a "feature". I'm using it in my apps and it works perfectly:
triggers on WILL rotate, not SHOULD rotate (waits until the rotate anim is about to start)
uses the Apple naming convention for landscape/portrait files (Default.png is Default-landscape.png if you want Apple to auto-load a landscape version)
reloads the new NIB
which resets the self.view - this will AUTOMATICALLY update the display
and then it calls viewDidLoad (Apple will NOT call this for you, if you manually reload a NIB)
(NB stackoverflow.com requires this sentence here - there's a bug in the code formatter)
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if( UIInterfaceOrientationIsLandscape(toInterfaceOrientation) )
{
[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:#"%#-landscape", NSStringFromClass([self class])] owner:self options:nil];
[self viewDidLoad];
}
else
{
[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:#"%#", NSStringFromClass([self class])] owner:self options:nil];
[self viewDidLoad];
}
}

Related

Showing a content of a UIView loaded from a xib in a storyboard

Current Setup
I have a subclass of a UIView that loads a content from a xib file - call it a XibOwnerClass. Also, among other classes, there is a class called Triangle which helps me to create triangles with or without a border, with different stroke or fill color etc. That class is designable in storyboard and some of its properties are defined as IBInspectable.
Currently, in my xib file, I use this triangle view and setup its inspectable properties through IB. And that is really cool and convenient... So if I look into xib, I will actually see the triangle view among other views.
So, lets go further. In order to use this XibOwnerClass, I drag the UIView element to the storyboard, and change its custom class to XibOwnerClass, so I get my designable properties specific for XibOwnerClass. So, I can setup all things in IB and when I run the app, everything works.
The problem
Even if this works, I wonder if there is a way, to have multiple views (of class XibOwnerClass) dragged on a storyboard, and to be able to configure all of them individually trough Interface Builder?
Currently when I drag the UIView and change its custom class to XibOwnerClass I see nothing. I mean, the view is there, and it has its standard properties + inspectable properties. But I can't see triangles defined in xib in this new view. Is there a way to do this?
I am aware that xib is reused in my case (and it is meant to be used like that), so if I change something in a xib, all views that load from it will be affected. But is there a way multiple views to load from the same xib, but when loaded, to see them & setup them individually?
Here is how I load from xib:
-(instancetype)initWithCoder:(NSCoder *)aDecoder{
if ((self = [super initWithCoder:aDecoder])){
UIView *myView = [[[NSBundle mainBundle] loadNibNamed:#"MyXib" owner:self options:nil] firstObject];
if (myView){
myView.frame = CGRectMake(myView.frame.origin.x, myView.frame.origin.y, self.frame.size.width, self.frame.size.height);
[self addSubview:myView];
//Initialize other stuff
[self commonInit];
}
}
return self;
}
Then in awakeFromNib: I use the values of inspectable properties...
EDIT:
Also I don't have initWithFrame: implemented, but I thought that it is not needed if load from nib (because it is not executed). But maybe this is needed while design time ?
can't really say what's wrong when you don't share class codes. but i guess you didnt use prepareForInterfaceBuilder. in your XibOwnerClass override prepareForInterfaceBuilder and call setups for view inside it for example change background colour or so.
override func prepareForInterfaceBuilder() {
//prepare setups or things to show in storyboard
}
Okay, I have solved it. It turned out that I shouldn't use Main Bundle but rather bundle for that class, like described here.
So here is the code:
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
#Mohammadalijf's answer in general is correct. It is just not a direct solution for my issue. But later on, when I fixed bundle thing, I used prepareForInterfaceBuilder like he said, and initialized things there so everything was available at design time.
Some useful stuff related to all this can be found here as well.

Why loadNibNamed:owner:options will automatically install on current view?

- (void)viewDidLoad
{
[super viewDidLoad];
UIView *testView = [[[NSBundle mainBundle] loadNibNamed:#"VBImageView" owner:self options:nil] lastObject];
NSLog(#"%d", [self.view.subviews count]); // result is 1
}
Something I don't understand is, just by calling loadNibNamed method will actually load imageView and add to current subview? Why? Can someone please explain that?
Updates
I found that in xib file, the Files's owner has the view delegate setup, If I remove this delegate, then the view will not automatically setup. what doest that view delegate mean?
I have now uploaded all the source files at here
I did the same thing but log 0 , so I think your view controller's view is not empty, could you try NSLog(#"%#", self.view.subviews) and show me the result?
for the second question, that view outlet just the property of your ViewController,that view will not automatically setup when disconnect because your window could not find that view to add.
EDIT:
That's too wired man, I download your source code and run it on simulator, it log 0 too.
after I delete loadFromNib method in viewDidLoad,it log 2.then I found there are two guide layout and one view in your storyboard.
By delete that view in storyboard, it log zero. so I think this is the reason .However, I not familiar with storyboard , so I can't completely explain it.sorry about that.

Reuse UI (xib files)

In my app I have two screens - first to show a user profile, the second - to edit the profile information. They are similar. I have completed the xib file for the first screen.
What's the best way to reuse it on second screen?
You should encapsulate the related elements as a custom view class. You can tackle this problem by creating views with code instead of just xibs, and I would recommend this.
But, if you would prefer to use a xib, you can create one that models the stuff you want to reuse. And then in your view controller call some code like this:
UIView* aView = [UIView alloc] initWithFrame .....];
[[NSBundle mainBundle] loadNibNamed:#"MyReusableComponent" owner:aView options:nil];
UILabel* someLabel = aView.injectedLabel; //this is alive after loading the xib
[self.view addSubView:aView];
When you create your xib, your need to set the Files Owner to a class that will respond to the setters for the properties that will be injected. (Eg your new view class). This way you can wire up the references.
For more information, look at Apple's examples of loading table cells from a xib - this is the same technique. When you load a xib and specify the owner, it will inject the values from the xib into the owner, in this case a custom view.
Dou you mean that you enter the view controller's edit mode and reuse those those elements you have created ?
Enabling Edit Mode in a View Controller
You could use UITextFields (instead of UILabels you may have logically used for show) that you change in appearance, and switch enabled on/off. As a minimal example:
Show:
self.textField.borderStyle = UITextBorderStyleNone;
self.textfield.enabled = NO;
Edit:
self.textField.borderStyle = UITextBorderStyleBezel;
self.textfield.enabled = YES;
You could of course do more on appearance, than just these basics.
In Xcode: Go to file > duplicate.
Then name your duplicated xib something like "editProfile" This will give a duplicate of your first xib that you can adjust as necessary

How do you load a new NIB file during autorotate?

Apple's NIBs don't support rotation natively, so I'm trying to use two different NIBs for the same view - one for each orientation.
I tried the following, and it works, except ... it breaks some subviews (e.g. Apple's OpenGL / GLKit views).
It ONLY breaks the very first rotation - all subsequent rotations work exactly as expected. So, I'm assuming there's a subtlety in Apple's stack of "viewWill/Did" calls that I'm missing here. Problem is, I've never been able to find real documentation on that - just vague references here and there inside Apple docs to "some of" the calls that happen, and when/why.
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if( UIInterfaceOrientationIsLandscape( toInterfaceOrientation) )
{
[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:#"%#-landscape", NSStringFromClass([self class])] owner:self options:nil];
[self viewDidAppear:FALSE];
[self viewWillDisappear:FALSE];
[self viewDidReload];
[self viewWillAppear:FALSE];
[self viewDidAppear:FALSE];
[self.navigationController.navigationBar setBackgroundImage:[UIImage imageNamed:#"navbar-landscape"] forBarMetrics:UIBarMetricsDefault];
}
else
{
[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:#"%#", NSStringFromClass([self class])] owner:self options:nil];
[self viewDidAppear:FALSE];
[self viewWillDisappear:FALSE];
[self viewDidReload];
[self.navigationController.navigationBar setBackgroundImage:[UIImage imageNamed:#"navbar-portrait"] forBarMetrics:UIBarMetricsDefault];
[self viewWillAppear:FALSE];
[self viewDidAppear:FALSE];
}
}
According to Apple, in your case you should not only load a different nib, but change the view controller:
If you want to present the same data differently based on whether a
device is in a portrait or landscape orientation, the way to do so is
using two separate view controllers.
Read the View Controller Programming Guide to find out how exactly.
Hope this helps!
If you search Stack Overflow for "[ios] rotate different nibs" you'll get a ton of hits. There are some, shall we say, "creative" solutions out there.
To Levi's point, as far back as iOS 4.3 at the very least, Apple's counsel has been "load a new controller with the orientation-specific NIB". In Xcode, you can install old iOS documentation sets, and if you look at the iOS 4.3 View Controller Programming Guide, and go to the "Creating an Alternate Landscape Interface" section, you'll see that their counsel back then is very similar to what we see now (though it's using NIBs and not storyboards). But the concept is the same. Fire up a new view controller if a different orientation. It's going to be the most robust approach, IMHO, as when I researched this a long time ago, it appeared that all of the techniques that involved loading the NIB in the background seemed to somewhat kludgy feel to them. Doing this modal to the landscape controller ensures that no critical system notifications or other operations slip through the cracks.
Personally, I find that enough of a hassle, that I have generally given up on separate NIBs for different orientations and stay with a single NIB/scene and rely on springs and struts (a.k.a. auto sizing masks) to do the vast majority of layout changes. And when I have a view for whose changes require more significant relocation of controls with respect to each other, changing of background images, etc, I'll programmatically change the layout in viewWillLayoutSubviews (which, if iOS version is less than 5.0, I invoke from willAnimateRotationToInterfaceOrientation ... but for iOS 5 and later, viewWillLayoutSubviews is superior).
I found the problem: this appears to be a bug specifically in Apple's OpenGL code. It's only projects using GLKit that are affected:
Apple's GLKViewController has to be the sole view controller, taking the full area of the Container VC (e.g. UINavigationController/UITabController/window's rootViewcontroller) and must be managing precisely one GLKView - if there are any others, and/or the GLKViewController isn't full screen, it breaks autorotation. (there appears to be some tricks Apple is doing when the GLKVC is hooked up as the one running the show, which don't fire otherwise)
In theory, if you create a custom Container View Controller (the new feature from iOS 5 that's currently only partially documented by Apple), then it will work correctly. But until Apple publishes full docs for that (maybe WWDC next year?) that's very hard to do.

Three20: loadView and viewDidLoad not called when restoring through TTNavigator

When using the the Three20 framework I have a problem with the way how TTNavigator seems to work. If in applicationDidFinishLaunching I restore the previous state of the app with:
TTNavigator* navigator = [TTNavigator navigator];
navigator.persistenceMode = TTNavigatorPersistenceModeAll;
navigator.window = self.window;
[navigator restoreViewControllers];
The methods loadView and viewDidLoad of the ViewController that was just restored never get called. How can that be so?
Is that a bug or by design?
If it's by design, what would be a good fix. My problem is that I want the ViewController to load its nib. I've seen other workarounds, but they are ugly and have outside component (like the app delegate instead of the view controller itself) load the nib, which I would like to avoid. An example of those ugly workarounds is given in the TTNibDemo example that ships with the Three20 source code.
It depends in what way you are calling viewController, try in viewWillAppear, should work.
Are you testing on device?
navigator.window = self.window;
_ [navigator restoreViewControllers];
On the device the first screen is always the first screen, whereas on the simulator that is not the case, and you should always check before with the condition
if(![navigator restoreViewControllers])
// do this
else
TTNavigationController* navi = [[((MyViewController1*)[navigator topViewController]) viewControllers] objectAtIndex:0];

Resources