When to use layoutSubview in iOS - iphone

I am writing iOS application for iPad that require custom layout.
The layout from portrait and landscape are totally difference, so it can't be solve by using UIAutoResizingMask.
I try to use the layoutSubview Method, but I detected that layout subview is called a lot (from UIScrollView).
How can i reduce the layoutSubview call to optimize the code , or I should call it by my self when ever the device is rotated.
Thank.

For different landscape and portrait design use view controllers methods such as
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
If you create your custom view depending on current orientation, check this orientation by UIDeviceOrientationDidChangeNotification notification and write appropriate code.
in one of the init~ methods:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didChangedOrientation:) name:UIDeviceOrientationDidChangeNotification object:nil];
And action
- (void) didChangedOrientation:(NSNotification *)sender{
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
if (UIDeviceOrientationIsPortrait(orientation)){}}

The fact that layoutSubviews gets called by a child UIScrollView is very unfortunate, but there's an (ugly) workaround:
#interface MyClass : UIView {
BOOL reallyNeedsLayout_;
}
#end
#implementation MyClass
- (void)setNeedsLayout
{
[super setNeedLayout];
reallyNeedsLayout_ = YES;
}
- (void)setFrame:(CGRect)rect
{
[super setFrame:rect];
reallyNeedsLayout_ = YES;
}
- (void)layoutSubviews
{
if (!reallyNeedsLayout_) return;
reallyNeedsLayout_ = NO;
// Do layouting.
}
#end
Not the best solution but seems to work reasonably well.

You should not do expensive calculations in layoutSubviews:

Speaking from experience I would personally only adjust your layout based upon deviceDidRotateSelector notifications.
I have an updatePortrait method and an updateLandscape method and call whichever is necessary.

Related

Detecting iOS orientation change instantly

I have a game in which the orientation of the device affects the state of the game. The user must quickly switch between Landscape, Portrait, and Reverse Landscape orientations. So far I've been registering the game for orientation notifications via:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
But it is far too slow - there seems to be about a second delay between rotating the phone and the notification actually being fired. I need a way to INSTANTLY detect changes in the device's orientation. I have tried experimenting with the gyroscope, but am not yet familiar enough with it to know whether or not it is the solution I am looking for.
Add a notifier in the viewWillAppear function
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(orientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];
}
The orientation change notifies this function
- (void)orientationChanged:(NSNotification *)notification{
[self adjustViewsForOrientation:[[UIApplication sharedApplication] statusBarOrientation]];
}
which in-turn calls this function where the moviePlayerController frame is orientation is handled
- (void) adjustViewsForOrientation:(UIInterfaceOrientation) orientation {
switch (orientation)
{
case UIInterfaceOrientationPortrait:
case UIInterfaceOrientationPortraitUpsideDown:
{
//load the portrait view
}
break;
case UIInterfaceOrientationLandscapeLeft:
case UIInterfaceOrientationLandscapeRight:
{
//load the landscape view
}
break;
case UIInterfaceOrientationUnknown:break;
}
}
in viewDidDisappear remove the notification
-(void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
[[NSNotificationCenter defaultCenter]removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
}
I guess this is the fastest u can have changed the view as per orientation
That delay you're talking about is actually a filter to prevent false (unwanted) orientation change notifications.
For instant recognition of device orientation change you're just gonna have to monitor the accelerometer yourself.
Accelerometer measures acceleration (gravity included) in all 3 axes so you shouldn't have any problems in figuring out the actual orientation.
Some code to start working with accelerometer can be found here:
How to make an iPhone App – Part 5: The Accelerometer
And this nice blog covers the math part:
Using the Accelerometer
Why you didn`t use
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
?
Or you can use this
-(void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
Or this
-(void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
Hope it owl be useful )
For my case handling UIDeviceOrientationDidChangeNotification was not good solution as it is called more frequent and UIDeviceOrientation is not always equal to UIInterfaceOrientation because of (FaceDown, FaceUp).
I handle it using UIApplicationDidChangeStatusBarOrientationNotification:
//To add the notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didChangeOrientation:)
//to remove the
[[NSNotificationCenter defaultCenter]removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil];
...
- (void)didChangeOrientation:(NSNotification *)notification
{
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if (UIInterfaceOrientationIsLandscape(orientation)) {
NSLog(#"Landscape");
}
else {
NSLog(#"Portrait");
}
}
Try making your changes in:
- (void) viewWillLayoutSubviews {}
The code will run at every orientation change as the subviews get laid out again.
#vimal answer did not provide solution for me. It seems the orientation is not the current orientation, but from previous orientation. To fix it, I use [[UIDevice currentDevice] orientation]
- (void)orientationChanged:(NSNotification *)notification{
[self adjustViewsForOrientation:[[UIDevice currentDevice] orientation]];
}
Then
- (void) adjustViewsForOrientation:(UIDeviceOrientation) orientation { ... }
With this code I get the current orientation position.

How do I update the screen before applicationDidBecomeActive?

I need to hide something on the screen when the user has activates the application by switching it to the foreground.
I have tried inserting my code within applicationDidBecomeActive or applicationWillEnterForeground and although it runs OK the old screen with the text I want to hide is displayed momentarily.
How can I hide the field before the screen is redrawn?
Thanks
iphaaw
I think the problem is, iOS will capture a screenshot from your app in the moment it goes to the background, so the animation will work in an instant.
The only way in my opinion to do this is to hide / cover your view in moment the app goes to the background.
Write some code in applicationWillResignActive: to 'hide' whatever you need to hide.
I faced a similar situation but, instead of hiding, I wanted to show a block code screen to grant access. Anyway I think that the solution also applies to your needs.
I often implement a custom base view controller in my iOS applications. So instead of dealing with applicationDidBecomeActive: or applicationWillResignActive: I setup this view controller to listen for the equivalent notifications:
#interface BaseViewController : UIViewController
- (void)prepareForGrantingAccessWithNotification:(NSNotification *)notification;
- (void)grantAccessWithNotification:(NSNotification *)notification;
#end
#implementation BaseViewController
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self addNotificationHandler:#selector(grantAccessWithNotification:)
forNotification:UIApplicationDidBecomeActiveNotification];
[self addNotificationHandler:#selector(prepareForGrantingAccessWithNotification:)
forNotification:UIApplicationWillResignActiveNotification];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)prepareForGrantingAccessWithNotification:(NSNotification *)notification {
// Hide your views here
myCustomView.alpha = 0;
// Or in my case, hide everything on the screen
self.view.alpha = 0;
self.navigationController.navigationBar.alpha = 0;
}
- (void)grantAccessWithNotification:(NSNotification *)notification {
// This is only necessary in my case
[self presentBlockCodeScreen];
self.view.alpha = 1;
self.navigationController.navigationBar.alpha = 1;
...
}
#end

Detect scrolling in Subclass of UIScrollView

Good morning,
I've creates a Subclass of UIScrollView and I now want to know when the user is scrolling in my subclass. For that I implemented it like the following:
ImageScroller.h
#interface UIImageScroller : UIScrollView <UIScrollViewDelegate> {
ImageScroller.m (within the #implementation)
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
NSLog(#"did scroll");
}
The problem is, that the method scrollViewDidScroll doesn't seem to get fired.
Is there any possibility to get it to work?
I also tried to set the delegate to it self, but it doesn't work.
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
self.directionalLockEnabled = YES;
[self setDelegate:self];
}
return self;
}
I added a ScrollView to my XIB-File and set the Class if it to my ImageScroller. Also I've set the Fileowner and I'm using the UIScrollViewDelegate in the .h-File of the ViewController as well as implementing the Method scrollViewDidScroll in the .m-file.
When I set the delegate of my ImageScroller in the code of the .m-file from the XIB like
[imageScroller setDelegate:imageScroller]
the scrollViewDidScroll is fired in my ImageScroller-Subclass, but the one in my ViewController isn't fired, but I need both.
Any solutions for that?
Thanks for your answers in advance.
I ran into the same problem creating a subclass of the UIScrollView but solved it this way. As mentioned above, you can set yourself (subclass instance) as the delegate however this is what I did.
In the sub class I overrode the following methods
//Override this to detect when the user is scrolling, this is also triggered during view
//layout. Here you can check your deceleration rate and determine when the scrolling has
//grinded to a halt.
-(void)setContentOffset:(CGPoint)contentOffset
//Override this to detect when the view has been resized (just a handy override)
-(void)setFrame:(CGRect)frame;
As far as the other UIScrollViewDelegate methods, the View Controller should be responsible for handling those in my opinion.
I think you can try setting self.delegate = self; to receive events.
If anyone is looking for a Swift answer, it's as simple as this:
override var contentOffset: CGPoint {
didSet {
if contentOffset != oldValue {
//same as scrollViewDidScroll
}
}
}

ViewController not responding to didRotateFromInterfaceOrientation

My view controller is not responding to didRotateFromInterfaceOrientation, despite that I have added following in my code:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
[self.popOver dismissPopoverAnimated:NO];
if (interfaceOrientation == UIInterfaceOrientationLandscapeRight) {
... My Custom Code...
}
}
Am I doing something wrong here?
If you can't inherit from UIViewController (which is unfortunate), you can use this:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
Then register to start receiving UIDeviceOrientationDidChangeNotification notifications.
If your UIViewController is a child in some root view then IB does not add it as a child controller to the root controller by default. The easiest way to address this is to modify your root controller:
- (void)viewDidLoad
{
[super viewDidLoad];
[self addChildViewController:(UIViewController*) self.yourChildController];
}
This should do the trick. Now your child controller will be receiving both:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
and
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation;
messages.
I think the real answer here (more accurately the answer to the linked question) is that you need to call
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
in your subclass implementation of the didRotateFromInterfaceOrientation method. For example:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
// Then your code...
}
This is not mentioned in the apple documentation but caused some serious and unexplained problems for me when omitted...

for the query of methods of rotation

I have 1 UIViewController.
in that i wrote,
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
NSLog(#"A");
} in UIViewController.
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
NSLog(#"b");
}
but this method is not called
why is it so?
You may need to do two things:
Tell device to track orientation changes: call [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
Implement your View controllers -shouldAutorotateToInterfaceOrientation method - return YES for orientations you want to support.