Two NIBs, same objects (iPhone) - iphone

So I am setting up my application rotation, and I have been setting up all of the button translations manually using the following method:
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)x
duration:(NSTimeInterval)duration{
CGRect bounds = [[self view] bounds];
if (UIInterfaceOrientationIsPortrait(x)) {
[anObject setFrame: CGRectMake(0, 0, bounds.size.width, 194)];
}
else {
[anObject setFrame: CGRectMake(0, 0, bounds.size.width, 110)];
}
}
I'm setting up each object's position in both orientations. It seems like a horrible way of accomplishing my goal..
So now what I'd like to do is make a copy of my main nib, and set up all of the buttons' positions in landscape on the new nib. Then when the device rotates, call the corresponding nib. Is that possible/feasible/an alright way to do it?
How would I call the nib for rotation? Can I use the exact same objects with the same IBconnections?
Is there a better way?

I've used a couple of different approaches depending on the behavior I needed. See which works best for you.
Find a set of rules which allow you to support both orientations using only autoresizingMask and autoresizesSubviews.
Find a set of rules for the parent view's -layoutSubviews such that the subviews can be rearranged relative to the parent view's bounds. That's more work than autoresizing but better than two hardcoded positions for every subview. This often requires the creation of a deeper view hierarchy to nest several views with different -layoutSubviews behaviors to get appropriate positioning of and spacing around the visible subviews.
Have two subviews, one for each orientation. Hide one when you rotate. This allows custom transitions between the subviews which you won't get if you load a new nib and replace the root view. If you load each subview from it's own nib you can then unload whichever one is no longer visible to reduce memory use. You then need to make sure both views display the same state, trigger the same IBActions, and have extra IBOutlet references (or carefully capture state from your current IBOutlets before rebinding them by loading a new subview from a nib) so you can set attributes of equivalent views in each rotation.
Combine options 2 and 3 above to load different views with different layout behaviors for each rotation and shuffle subviews back and forth between them.

This is a much easier way - 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 (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

What exactly is done to a UIView on interface rotation?

could someone explain what iOS does on rotation of the interface. I´ve got a layout problem with one View that is gone after rotating the iPhone. Seems that the View got set a new frame, bounds or whatever, don´t know. Anyhow after the interface was rotated once the layoutproblem is gone forever. So something must be set to the view at the time the interface rotates.
I´m loading the View from a NIB file and show it with a navigationcontroller:
BirthdayReminderWidgetConfigViewController *vc = [self.storyboard
instantiateViewControllerWithIdentifier:#"BirthdayConfigController"];
self.navigationController pushViewController:vc animated:true];
Maybe there is some setting I have to do in order to show up the view correct without rotating the interface.
I´ve got a layout problem with a view that is loaded from a nib file as follows:
The project contains a MainStoryboard. Within that, I load a view from a nib file.
NSArray *xib = [[NSBundle mainBundle] loadNibNamed:#"View" owner:self options:nil];
self.configViewController = [xib objectAtIndex:0];
The storyboard has a navigation controller and the loaded view is shown like this:
if (currentWidgetConfigViewController != nil)
{
[self.navigationController pushViewController:currentWidgetConfigViewController animated:true];
}
So in my opinion nothing wrong? (First question)
But now the problem.
What I designed is that:
What iOS does is the following
The controls are not arranged well.
And besides that with a button on the new view I open up a PeoplePicker with that code:
[self presentViewController:self.picker animated:YES completion:nil];
After closing the People picker with [self.picker dismissViewControllerAnimated:true completion:nil]; I get this result:
So what is going wrong here?
Override -setFrame: and -setBounds: in your view to see what happens:
- (void) setFrame: (CGRect) newFrame
{
NSLog(#"New frame: %#", NSStringFromCGRect(newFrame));
[super setFrame:newFrame];
}
Also, the transform usually changes during a rotation. Orientation Zoo might help, it’s a sample Xcode project showing what happens in various rotation use cases. Didn’t touch it for a long time, though, so I don’t know if it still works.

Remove or change color of frame around UIWebView document display?

I'm using a UIWebView to display a variety of file types (so I can't use a specialized PDF viewer) embedded into my main view (so I can't use a modal document interaction controller). My main view has a background design that clashes with the light gray frame that appears around documents in the UIWebView. Does anyone know a way to remove that gray frame, make it transparent or change its color?
I'm familiar with and have used the techniques for changing the background color of the UIWebView to avoid a "color flash" while it loads, and for removing the top and bottom shadow that appear when "overscrolling" the web view, but I haven't seen anyone address this gray frame. (It only appears when displaying documents like .doc or .pdf, not when displaying HTML content.) I've hidden all the images that are subviews of the UIWebView's scroll view, so apparently this is coming from somewhere else.
This question was written while iOS 4 was current, but in iOS 5 and later, simply setting the backgroundColor and opaque properties of the UIWebView will remove that gray frame:
myWebView.opaque = NO;
myWebView.backgroundColor = [UIColor clearColor];
You can change WebView's background color by assigning its backgroundColor property:
myWebView.backgroundColor = [UIColor redColor]; // will make red bakground
If you want transparency use [UIColor clearColor] instead. But remember - by making your views tranparent you can worsen app's performance.
Since this has been around for a while I'll just throw some ideas out there.
You may be able to utilize the stringByEvaluatingJavaScriptFromString: method of the UIWebView class to manipulate the document object model if the UIWebView treats the loaded document as a page, but this method has some limitations.
Other than that, the only thing you really have left at your disposal is manipulating the view hierarchy to modify any subviews of the UIWebView, but there may not be anything for you manipulate if the UIWebView class renders the viewer directly rather than creating a hierarchy.
It looks like it is not possible to remove the border from a UIWebView but could you not use CGContextDrawPDFPage() as Apple show here: http://developer.apple.com/library/ios/#samplecode/ZoomingPDFViewer/Introduction/Intro.html
Try this,
self.webview.backgroundColor = [UIColor whiteColor];
for (UIView* subView in [self.webview subviews])
{
if ([subView isKindOfClass:[UIScrollView class]]) {
for (UIView* shadowView in [subView subviews])
{
if ([shadowView isKindOfClass:[UIImageView class]]) {
[shadowView setHidden:YES];
}
}
}
}
This may help you. :-)
You need a bunch of CoreAnimation magic:
- (void) hideShadowInLayer:(CALayer *) layer
{
for (CALayer *l in layer.sublayers) {
l.shadowOpacity = 0;
[self hideShadowInLayer:l];
}
}
- (void) hideShadows
{
[CATransaction begin];
[CATransaction setValue:(id) kCFBooleanTrue forKey:kCATransactionDisableActions];
[self hideShadowInLayer:webView.layer];
[CATransaction commit];
}
You need to perform hideShadows method somewhere AFTER loading your document and while you are scrolling it (I guess scrollViewDidScroll of webView.scrollView.delegate is a good place). You also need to include QuartzCore framework to your project.
What's going on here:
Any view uses something called layer for rendering. Layers can have its own hierarchy and every layer can have its own border and shadow, so the frame that annoying you is the shadow of one of them. Bad thing - UIWebView recreates it while scrolling - so you need to use this method constantly. And I guess shadowOpacity has a default animation attached to it, so you need CATransaction to disable it.

UISlider subclass causing UI hang in only one UIView of universal app

I have been working on the front-end to a universal iOS app, setting up separate iPhone and iPad view controllers as subclasses of a XIB-less view controller (as described in http://www.kotancode.com/2011/04/05/ios-universal-apps/). Each subclassed view controller contains a device-specific XIB, with each XIB containing portrait and landscape UIViews which are switched upon orientation change (I followed http://aseriesoftubes.com/articles/ipad-development-101-orientation/ for this). The controls common to all four UIViews are synchronised with an IBAction method linked through IBOutletCollections.
Just today I have created a UISlider subclass for my own slider design, which appears to work fine, apart from one large issue: when testing with the iPhone in the simulator starting in portrait orientation the slider works fine; when switched to landscape it is also fine; however when I switch back to portrait the slider works for up to two operations and then freezes, taking some other controls of the view with it; stranger still, if I switch back to landscape the slider works fine! This happens only with the iPhone simulator, not the iPad.
I have tried switching IB's 'Continuous' option on and off, and tried both the 'Touch Up Inside' and 'Value Changed' connections from my slider's Sent Events, with no change (as it will be for a volume control I will need 'Value Changed' to work). My slider's implementation code is thus:
#implementation VolumeSlider
- (id)initWithCoder:(NSCoder *)decoder
{
if((self = [super initWithCoder: decoder])) // Double paretheses to silence warning
{
// Instantiate slider images
UIImage *thumbImage = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource: #"volume thumb" ofType: #"png"]];
UIImage *trackImage = [[UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource: #"volume track" ofType: #"png"]] stretchableImageWithLeftCapWidth: 20 topCapHeight: 0];
// Set control elements with images
[self setThumbImage: thumbImage forState: UIControlStateNormal];
[self setMinimumTrackImage: trackImage forState: UIControlStateNormal];
[self setMaximumTrackImage: trackImage forState: UIControlStateNormal];
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
#end
The two action methods that the sliders trigger are:
- (IBAction)volumeChange:(id)sender
{
NSLog(#"VOLUME slider changing");
// VOLUME slider action
}
- (IBAction)volumeChangeFinish:(id)sender
{
NSLog(#"VOLUME change finished");
VolumeSlider *incomingVolume = sender;
for(VolumeSlider *volumeSlider in self.volumeSliders)
{
volumeSlider.value = incomingVolume.value;
}
}
(volumeChange will use the slider value to change volume in real time, volumeChangeFinished will update the other sliders in the view on touch inside up.)
This one has me stumped – it feels like a memory issue but I can't figure out exactly where. Does anyone have an idea what could be causing this?
(Mac OS 10.6.8, Xcode 4.0.2, building for iOS 4.3)
UPDATE ---
I have tried instantiating a new, stock UISlider into the iPhone view and this suffers the same problem, without having been connected up to anything. This points towards an issue with the view controller, or even some bug.
Although I have no definitive reason for the issue, I have managed to fix this. Basically I created a new UIView for the iPhone portrait orientation in Interface Builder, rebuilt the interface and connected it up in place of the dodgy one and the issue was gone. I don't know if I could have had a corrupted view in the XIB, that's the only answer I can proffer.

UIView addsubview after orientation change: Tell view to resize

I have a UIView as a XIB in Portrait mode.
This view is added programmatically to the viewcontroller like this:
NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:#"InputView" owner:self options:nil];
InputView *inputView = (InputView*)[nibObjects objectAtIndex:0];
[self.view addSubview:inputView];
This view has autoresizing masks set up properly and rotates fine when the orientation changes from Portrait to landscape.
However, if the orientation is already landscape and I create the view after the orientation change, it has its initial portrait orientation.
Is there a way to tell the view to initialize or resize itself to portrait by using its masks?
Thanks in advance for any reply!
EDIT:
Using the suggestions of occulus and Inder Kumar Rathore (thanks guys!), I altered the code to this:
InputView *inputView = (InputView*)[nibObjects objectAtIndex:0];
[self.view addSubview:inputView];
[self.view setNeedsLayout];
[self.view layoutSubviews];
[self.view layoutIfNeeded];
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
[self shouldAutorotateToInterfaceOrientation:orientation];
Unfortunately, there is no change at all.
I think I found someone asking the same question:
When adding a sub view the view does not resize if the app is in landscape mode
The answer identifies the problem correctly, but is not very encouraging...
Sure, I could create two nibs or resize the frame, but this seems so contrary to the idea of auto-resizing.
I find it hard to believe that there is no way to tell a nib after awakening and adding it to a view to use its autoresize features...something that works flawless when the device rotates.
EDIT 2:
The solution of idz works:
InputView *inputView = (InputView*)[nibObjects objectAtIndex:0];
[self.view addSubview:inputView];
inputView.frame = self.view.bounds;
[inputView show];
Thanks!
Often a NIB/XIB file contains a UIViewController that takes care of all of this. In this case, since their is no view controller (in the NIB/XIB) you need to take over its post-load duties.
Calling layoutSubviews directly, or indirectly via setNeedsLayout or layoutIfNeeded won't do you much good because the default implementation does nothing.
Assuming you want input view to fill the bounds of self.view you do the following:
InputView *inputView = (InputView*)[nibObjects objectAtIndex:0];
[self.view addSubview:inputView];
inputView.frame = self.view.bounds;
[inputView show];
All the resize masks of the sub-views must be correctly set for this to work and, obviously, if you don't want to fill the full bounds you may want to adjust the frame.
[self.view addSubview:viewSpinner];
viewSpinner.frame = self.view.frame;
[viewSpinner setNeedsLayout];
This works for me (Y)
I don't know if you still have this issue.
Let's say you have the following architecture:
window
subviewcontroller
(you implemented shouldautorotate correct to answering the wanted orientations)
Into this subviewcontroller you want to add the views of new UIViewControllers by just calling the addSubview function.
Instead of implementing the bounds manipulation in shouldautorotate, you should implement it in
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
self.newUIViewController.view.frame = self.view.bounds;
}
didRotateFromInterface... is called after shouldRotate. In this function the bounds are already setup correctly.
This way you don't need so much manipulation by code.
See this related question:
When do autoresizing masks take effect in iOS?
So after loading your new view from the nib, and adding as a subview to self.view, try calling setNeedsLayout.

iPhone OS: Tap status bar to scroll to top doesn't work after remove/add back

Using this method to hide the status bar:
[[UIApplication sharedApplication] setStatusBarHidden:YES animated:YES];
When setting "hidden" back to NO, the tap-to-scroll-to-top (in UIWebView, UITableView, whatever) doesn't work any more, and requires a restart of the app to get the functionality back.
Is this a bug (I filed a rdar anyhow) or have I missed a step? Should I perhaps expect this behavior since the statusBar "loses touch" somehow with the respective view?
You could try setting the ScrollsToTop property to true again after re-showing it:
[currentView setScrollsToTop:YES];
If that's not working, are you definitely only showing one view? If there is more than one scrolling view a scrollViewDidScrollToTop message is ignored...
In iOS 5.0 you can access the scrollview property of the UIWebView
webView.scrollView.scrollsToTop = YES;
The following fix by Alex worked for me. Thanks!
((UIScrollView *)[[webView subviews] objectAtIndex:0]).scrollsToTop = NO;
Being in a hurry this fix worked great, however given more time I might've subclassed the UIWebView and accessed the protected UIScrollView member directly.
The worry I have with Alex' method is that it assumes that UIScrollView is at index zero of the subviews (encapsulation allows private members to change). Which suggests another solution still:
for (UIView* v in [webView subviews])
{
if ([v isKindOfClass:[UIScrollView class]])
{
(UIScrollView *)v.scrollsToTop = NO;
}
}
I was having a similar problem where the scroll-to-top functionality was lost. Turns out this will only work when you have only one active view at a time (within the same scroll view). In my case I had a table view and another view which would fade in/out. Adding a removeFromSuperview at the end of the animation did the trick.
The answer was in the UIScrollView.h file comments:
/*
this is for the scroll to top gesture. by default, a single scroll visible scroll view with this flag set will get the call. if there is more than one visible with this
flag set or the delegeat method returns NO, the view isn't scrolled
*/
#property(nonatomic) BOOL scrollsToTop; // default is YES. if set, special gesture will scroll to top of view after consulting delegate
You can use the following code to have the UIWebView ignore scrollToTop without the extra UIScrollView:
((UIScrollView *)[[webView valueForKey:#"_internal"] valueForKey:#"scroller"]).scrollsToTop = NO;
I had a similar problem after playing a Youtube video within my app. scrollsToTop was still set to YES but tapping the status bar had no effect.
I finally realised that my app window was no longer the key window. After adding the following line to a UIWindow subclass (which I already had for other reasons) everything worked as it should again:
if (![self isKeyWindow]) [self makeKeyWindow];
I just ran across a similar behavior in the app I'm currently working on. In its case, if you load a YouTube video from within a UIWebView, scroll to top stops working for the rest of the application's life cycle. I kind of assume this might happen after loading the movie player as well, but haven't confirmed. That functionality has been around a lot longer and probably has fewer bugs.
When there are multiple scrollview, you can also set scrollUpToTop to NO for the others scrollview. cf:
setScrollsToTop with multiple UIScrollView classes and/or subclasses(UITableView)
I want to add my case, I add an UIWebView on an UIScrollView, as h4xxr had answered on the top:
If there is more than one scrolling view a scrollViewDidScrollToTop message is ignored
So, I get a simply way to make it work on webView: just set the scrollView·s scrollsToTop property false.
And when tap the status bar, it won`t got intercepted by the scrollView, and the webView scrolls to the top!
UIScrollView *scrollView = [[UIScrollView alloc] init];
scrollView.frame = self.view.bounds;
scrollView.scrollsToTop = false; //igore scrollView`s scrollsToTop
[self.view addSubview:scrollView];
UIWebView *webView = [[UIWebView alloc] init];
webView.frame = scrollView.bounds;
[scrollView addSubview:webView];