frame size does not change in landscape mode - iphone

I have a UINavigationController that can rotate to landscape mode and that it initializes a new UIViewController and pushes it on the stack. On the UIViewController's viewDidLoad I just print the self.view.frame.size.
If the UINavigationController is in Portrait mode, the View Controller will print {320, 460}. However, when the Navigation Controller is in Landscape, the frame size is exactly the same. (To clarify, in the second case the UIViewController is initialized while the phone is already in landscape mode.)
Shouldn't the frame size of the view be rotated? And if not how I can I find the correct frame size without hardcoding the numbers?

You can't rely on the frame in landscape mode; you have to use a combination of bounds and center; frame is generated using a combination of those, and when there's a non-identity transform applied (as there is in landscape), it gets a little weird.

first you need to set your view to resize automatically with a proper autoresizingMask.
with this your view will adapt to the size of the controller itself.
you can check this yourself with an NSLog. But don't put it in loadView, this is too early. Put this in viewWillAppear.
If you set the autoresizingMask of your view with Interface Builder you should turn off the Simulated Interface Elements in the Attributes inspector. If any of these is on you can't change the autoresizingMask in the Size inspector.

This is a pain and it's still true in iOS 4.2 for iPads. The way I solved this is to subclass the UIView associated with the UIViewController. I did this in interface builder but I suppose one could also do this in code somehow. In interface builder select the UIView in the UIViewController then hit the (i) icon in the upper right of the Inspector window. Under class identity hit the popup and chose the UIView subclass below.
The approach is that this UIView subclass overrides the layoutSubviews method, finds the next UIViewController, determines if it implements a reactToLayout method (which is a method that you have to implement in the UIViewController subclass for the view controller of this view). If the reactToLayout method exists in the first UIViewController found, it is invoked.
The reactToLayout method in the view controller then does whatever one needs to do which it will be able to do successfully since the view's frame is set up properly by this time (unlike in ViewDidLoad, viewWillAppear, or even viewDidAppear). I have a method that I call anytime the orientation or frame changes. It's a pain but I store the last frame laid out and last orientation laid out in internal variables of the view controller. The internal layout for new orientation or frame change method compares these to the view's current frame and requested or current orientation so that it doesn't unnecessarily layout stuff over and over.
Here's the code:
UILayoutSubviewsView.h
#import <UIKit/UIKit.h>
#interface UILayoutSubviewsView : UIView {
}
#end
UILayoutSubviewsView.m
#import "UILayoutSubviewsView.h"
// Create this to avoid a warning that this method does not exist for UIViewControllers
// this is OK since we check to see that it does exist before invoking it
#interface UIViewController(UndocumentedMethodForUIViewController)
-(void) reactToLayout;
#end
#implementation UILayoutSubviewsView
// Pass this up to our view controller if it supports the reactToLayout method
// (this is the whole reason for the class)
-(void) layoutSubviews {
[super layoutSubviews];
// Look for the first next responder that is a UIViewController
UIViewController *ourViewController = nil;
id myNextResponder = [self nextResponder];
while (myNextResponder != nil && ourViewController == nil) {
if ([myNextResponder isKindOfClass:[UIViewController class]]) {
ourViewController = myNextResponder;
}
else {
myNextResponder = [myNextResponder nextResponder];
}
}
// If we got a view controller, then see if it supports the reactToLayout method
if (ourViewController != nil) {
if ([ourViewController respondsToSelector:#selector(reactToLayout)]) {
// Invoke the view controller's reactToLayout method
[ourViewController reactToLayout];
}
}
}
#end
YourViewController.h
#import <UIKit/UIKit.h>
#interface YourViewController : UIViewController {
CGRect lastLayedOutFrame;
UIInterfaceOrientation lastLayedOutOrientation;
}
#pragma mark -
#pragma mark Instance Methods
-(id) init;
-(void) reactToLayout;
#end
YourViewController.m
#import "YourViewController.m"
#pragma mark Private Interface Category
#interface YourViewController()
-(void) setViewForCurrentFrameAndRequestedOrientation:(UIInterfaceOrientation) interfaceOrientation;
#end
#implementation YourPadViewController
-(id) init {
// First our super then set ourselves up
if (self = [super initWithNibName:#"YourViewController" bundle:nil]) {
// Initialize some basic stuff
lastLayedOutFrame = CGRectZero;
lastLayedOutOrientation = UIDeviceOrientationUnknown;
}
return self;
}
-(void) viewWillAppear:(BOOL) animated {
[super viewWillAppear:animated];
// Make sure we're showing the right stuff in the right place
[self setViewForCurrentFrameAndRequestedOrientation:UIDeviceOrientationUnknown];
}
-(void) viewDidAppear:(BOOL) animated {
[super viewDidAppear:animated];
// Make sure we're showing the right stuff in the right place
[self setViewForCurrentFrameAndRequestedOrientation:UIDeviceOrientationUnknown];
}
-(void) reactToLayout {
// Make sure we're showing the right stuff in the right place
[self setViewForCurrentFrameAndRequestedOrientation:UIDeviceOrientationUnknown];
}
#pragma mark -
#pragma mark Rotation Support
-(BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation) interfaceOrientation {
return YES;
}
// This is called right before the actual rotation
-(void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation) interfaceOrientation duration:(NSTimeInterval) duration {
[super willAnimateRotationToInterfaceOrientation:interfaceOrientation duration:duration];
// Make sure we're showing the right stuff in the right place
[self setViewForCurrentFrameAndRequestedOrientation:interfaceOrientation];
}
// Make the necessary adjustments for the different view orientations
-(void) setViewForCurrentFrameAndRequestedOrientation:(UIInterfaceOrientation) interfaceOrientation {
// Set up the requested orientation (need this to handle the Unknown case)
UIInterfaceOrientation requestedOrientation;
if (interfaceOrientation != UIDeviceOrientationUnknown) {
requestedOrientation = interfaceOrientation;
}
else {
requestedOrientation = [[UIDevice currentDevice] orientation];
}
// See if we have anything to do
if (!(CGRectEqualToRect(self.view.frame, lastLayedOutFrame) && lastLayedOutOrientation == requestedOrientation)) {
// Do whatever needs to be done
// Record our last layed out frame and orientation
lastLayedOutFrame = self.view.frame;
lastLayedOutOrientation = requestedOrientation;
}
}

Hi every one
I think there is a simple solution that do the job for me
you can use currentSize instead of self.view.frame.size
in yourClass.h
#interface yourClass : UIViewController {
CGSize currentSize;
}
#property (nonatomic, readwrite)CGSize currentSize;
#end
in yourClass.m
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
if (toInterfaceOrientation != self.interfaceOrientation) {
CGSize newSize;
// 20 is the status bar height
newSize.width = self.view.bounds.size.height + 20;
newSize.height = self.view.bounds.size.width - 20;
currentSize = newSize;
//any other necessary code
}
}

Related

UIPageViewController check when swiped back or forward

I want to keep track of the index using the UIPageViewController. Whenever I swipe I need to index++ or index--. This delegate method gets called whenever you swipe back or further:
- (void)pageViewController:(UIPageViewController *)pvc didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
// If the page did not turn
if (!completed)
{
// You do nothing because whatever page you thought
// the book was on before the gesture started is still the correct page
return;
}
// I want to check here whenever the page was swiped back or further
}
How do I check in this method if the user swiped back or further? I know there are the 2 DataSource methods "viewControllerAfterViewController" and "viewControllerBeforeViewController" but I cannot check if the page transition has completed (and I can do this in the above method) any idea how I could know if the user swiped back or further in the above method?
use protocol:
MyClass : UIViewController <UIPageViewControllerDataSource,UIPageViewControllerDelegate
declare a atributes:
#property(nonatomic) NSInteger currentIndex;
#property(nonatomic) NSInteger nextIndex;
and in the methods:
-(void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers{
NewsTableViewController *controller = [pendingViewControllers firstObject];
self.nextIndex = [self.arrViews indexOfObject:controller];
}
-(void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed{
if (completed) {
self.currentIndex = self.nextIndex;
}
self.nextIndex = 0;
}
there you will have the current page.
Thanks to Corey Floyd in enter link description here
According to the documentation there does not appear to be a way to tell whether the user has swiped the page forward or backward. The boolean 'finished' will tell you whether or not the user has completed the page turn.
A workaround:
Create an int variable and using the viewControllerAfterViewController and viewControllerBeforeViewController methods either increase or decrease the value of the variable. Use that to test whether or not they moved forward or backward.
Edit: You could use presentationIndexForPageViewController from the documentation
Edit 2: Check this link here There is a method named setViewControllers:direction:animated:completion: the direction will be either UIPageViewControllerNavigationDirectionForward or UIPageViewControllerNavigationDirectionReverse
Edit 3: Code - This is assuming you know which view controller will be called by either going forward or backward:
Create a variable on your appDelegate and a setter method:
int indexVar;
- (void)setIndex:(int)indexVar;
Then on your view controllers (forward or backward) either increase the value or decrease the value (viewDidLoad):
(AppDelegate *)[[UIApplication sharedApplication] delegate] setIndex:<whatever>];
Something along those lines. This won't be an exact way to accomplish your goal, but hopefully it will get you headed in the right direction.
I did it by creating protocols in each of my ViewController classes, with the protocol method called in the viewWillAppear method. Then in the PageViewController whenever I instantiate one of the view controllers I set its delegate to be the PageViewController.
This is the 3rd ViewController in my project(Note that I've done this in each of my view controllers)
#class ViewController3;
#protocol ViewControllerPageNumber <NSObject>
-(void)viewWillAppearMyPageNumber:(int)myPageNumber;
#end
#interface ViewController3 : UIViewController
#property (nonatomic, weak) id <ViewControllerPageNumber> delegate;
#end
and in the .m file in the viewWillAppear method
-(void)viewWillAppear:(BOOL)animated{
[self.delegate viewWillAppearMyPageNumber:3];//The 3 will be different for each ViewController
}
Next, in the PageViewController.m, whenever I instantiate a view controller I set its delegate to be self( or PageViewController). viewCons is just an array of strings with my viewControllers names.
- (UIViewController *)viewControllerAtIndex:(NSUInteger)index {
id vc = [[NSClassFromString([viewCons objectAtIndex:index]) alloc] init];
if([vc isMemberOfClass:[NSClassFromString(#"ViewController3") class]]){
ViewController3 *vc3=(ViewController3 *) vc;
vc3.delegate=self;
}else if([vc isMemberOfClass:[NSClassFromString(#"ViewController2") class]]){
ViewController2 *vc2=(ViewController2 *) vc;
vc2.delegate=self;
}else if([vc isMemberOfClass:[NSClassFromString(#"ViewController") class]]){
ViewController *vc1=(ViewController *) vc;
vc1.delegate=self;
}
return vc;
}
Finally, I'm implementing my custom delegate method, which in my case is refreshing labels' text I have set on top of the PageViewController.
-(void)viewWillAppearMyPageNumber:(int)myPageNumber{
[self refreshLabelsOnCurrentPageWithIndex:myPageNumber];
}
I think the easiest solution is to to create an own ViewController class with a property that keeps track of the currently shown index. In most of the cases I need a custom ViewController for my PageViewController anyways. In my example this is the following class.
#interface PageZoomViewController : UIViewController
#property (nonatomic) int pageIndex;
#end
Then in the viewControllerAfter/Before methods you can pass the index to the new page.
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
int nextPageIndex = ((PageZoomViewController *)viewController).pageIndex-1;
PageZoomViewController *controller = [[PageZoomViewController alloc] initWithPageViewControlParent:self andFrame:[self frameForPagingScrollView] andPageIndex:nextPageIndex];
return controller;
}
When the animation for the next page finished you can easily set the current index like this.
-(void)pageViewController:(UIPageViewController *)thePageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {
if(completed) {
index = ((PageZoomViewController *)thePageViewController.viewControllers[0]).pageIndex;
}
}
Hope this helps!

Objective-C how to disable user interaction selectively

I have a Main View Controller that has many subviews. What I want is to disable all other views except one subview and its subviews programmatically from the subview file. But all I get is all frozen views. What did I do wrong?
I tried this code:
#define kDontDisableUserInteraction 321
- (id)initWithFrame:(CGRect)frame
{
NSLog(#"initWithFrame");
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.tag = kDontDisableUserInteraction;
}
return self;
}
-(void)something{
MVC *myController = [self getMVC];
for (UIView* subview in myController.view.subviews) {
NSLog(#"subview.tag %i", subview.tag);
if (subview.tag != kDontDisableUserInteraction){
subview.userInteractionEnabled = NO;
}
}
for (UIView *view in self.subviews){
NSLog(#"enabled!");
view.userInteractionEnabled = YES;
}
}
- (MVC *)getMVC {
Class vcc = [MVC class]; // Called here to avoid calling it iteratively unnecessarily.
UIResponder *responder = self;
while ((responder = [responder nextResponder])) if ([responder isKindOfClass: vcc]) return (MVC *)responder;
return nil;
}
Following links may be helpful:
How to disable touch input to all views except the top-most view?
UIView -- "user interaction enabled" false on parent but true on child?
I solved it by applying a full screen of a button on all other views and get the one view that I want to have user interaction upon the button. This way I disallow the user to click on any function except the one view I want the user to click on certain functions.

viewDidLoad method not being called using presentModalDialog method

I am using presentModalDialog to show a viewcontroller's view however the viewDidLoad never gets called?!
Is there any method that is called where I can place my logic that fills and configures the view?
EDIT:
It's a bit difficult to put a small amount of code, you kind of need to see it all, but here goes:
I have 2 nibs and 2 view controllers (portrait-mainvc/landscape) which both inherit from 1 baseclass which has the logic and the iboutlets. This is to allow me to re-use code. When the orientation changes in the main controller, it switches between the 2 controllers (modal dialog) which in turn use their respective nib's however they all use the same base code to configure the UI items.
#implementation HomeViewControllerBase
- (void)configureBestSellItems
{
[self startRetrievingRegions];
// load all the images from our bundle and add them to the scroll view
// NSUInteger i;
for (int i = 0; i <= 150; i++)
{
NSString *imageName = #"tempImage.jpg";
UIImage *image = [UIImage imageNamed:imageName];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
// setup each frame to a default height and width, it will be properly placed when we call "updateScrollList"
CGRect rect = imageView.frame;
rect.size.height = kScrollObjHeight;
rect.size.width = kScrollObjWidth;
imageView.frame = rect;
imageView.tag = i; // tag our images for later use when we place them in serial fashion
[self.bestSellScrollView addSubview:imageView];
[imageView release];
}
[self layoutScrollImages]; // now place the photos in serial layout within the scrollview
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
[super viewDidLoad];
[self configureBestSellItems];
}
#end
#interface HomeViewController : HomeViewControllerBase
{
}
#interface HomeViewController_Landscape : HomeViewControllerBase
{
}
#implementation HomeViewController
//This works for portrait
- (void)viewDidLoad
{
[super viewDidLoad];
}
#end
#implementation HomeViewController_Landscape
//This does not work
- (void)viewDidLoad
{
[super viewDidLoad];
}
//This works as suggested
- (void)awakeFromNib
{
[super configureBestSellItems];
}
#end
You can try these in your viewcontroller:
-(void)awakeFromNib
{
[super awakeFromNib];
// your code here
}
or
-(void) viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
// your code here
}
However, you should try to find why viewDidLoad is never called. You can also check this
link.
You may have a custom function in there named presentModalDialog:, as searching apple's docs doesn't have that function. You're calling a custom function that loads a Xib.
I'm doing the same thing.
You want this:
[self presentModalViewController:controller...
ViewDidLoad is called by Controllers, if the awakeFromNib is being called, you're probably getting, by itself, the view.
It's a common problem in objective c:
you should review the flowing:
1-initiation of the view controller you should initialize it in the right way in app delegate.
2-review the file owner of the view it must be the right class which usually the same name of uiview.

iPhone SDK: detect view that was tapped

In my application I have a lot of little UIImageViews and a view off to the side. What I need is that view off to the side to be able to detect which image view was tapped and give me all the information about it (much like with (id)sender) as well as its coordinates. What is the best way to do this?
This is the internal class from my project. I think you get the idea.
As an option you can create protocol for the owner (or delegate).
You can obtain coords using
-[UITouch locationInView: someView]
Here is the code:
#interface _FaceSelectView : UIImageView {
#protected
VIFaceSelectVC* _owner;
}
-(id) initWithOwner:(FaceSelectVC*) owner;
#end
#implementation _FaceSelectView
-(id) initWithOwner:(FaceSelectVC*) owner {
if( self = [super init] ) {
_owner = owner;
self.userInteractionEnabled = YES;
}
return self;
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[_owner _touchesBeganIn: self withTouch: (UITouch*)[touches anyObject]];
}
-(void) touchesEnded:(NSSet*) touches withEvent:(UIEvent*) event {
[_owner _touchesEndedIn: self withTouch: (UITouch*)[touches anyObject]];
}
-(void) touchesMoved:(NSSet*) touches withEvent:(UIEvent*) event {
[_owner _touchesMovedIn: self withTouch: (UITouch*)[touches anyObject]];
}
#end
I would subclass UIImageView and add a new property for the target view you would like to message. Then reenable userInteractionEnabled and add a action to touchesUpInside.
In the action method of the custom subclass call a method on the target view in which you also give the object of the custom subview. Basically you use delegation and pass in the delegate call all parameters you need.
Add tag to UIImageView like myUIImageView.tag = intNumber;
and in side touchesBegan or you can call any common method for all your UIImageView and use tag to identify which view is tapped.

How to add a custom iPhone UI element that is not an IB component?

I'm trying to create an application that uses the iPhone MapView (under Google Code). I can't figure out how to integrate this thing into my application without handwriting the entire UI, ie not using the IB at all.
How can I use the Interface builder to create all of my menus and such but add the MapView in? Do I need to edit the MapView to make it an IB component?
Thanks!
EDIT:
#pgb
Here is my code, it still just displays a blank UIView, I have connected everything up on the IB side.
//
// NewTestViewController.h
// NewTest
//
#import <UIKit/UIKit.h>
#interface NewTestViewController : UIViewController {
UIView* mapPlaceholder;
}
#property (nonatomic, retain) IBOutlet UIView* mapPlaceholder;
#end
//
// NewTestViewController.m
// NewTest
//
#import "NewTestViewController.h"
#import "MapView.h"
#implementation NewTestViewController
#synthesize mapPlaceholder;
// The designated initializer. Override to perform setup that is required before the view is loaded.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
MapView *mapView = [[[MapView alloc] initWithFrame:
[[UIScreen mainScreen] applicationFrame]] autorelease];
[mapPlaceholder addSubview:mapView];
}
return self;
}
/*
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
}
*/
/*
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
}
*/
/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[mapPlaceholder.subviews release];
[super dealloc];
}
#end
NEVERMIND FIGURED IT OUT THANGS PBG
You can probably create the whole interface in IB, add an empty UIView as the placeholder view, and then use addSubview: to add the MapView instance to the view hierarchy.
Your placeholder view can be defined as an IBOutlet, so you can then add the MapView from your UIViewController instance.
In IB you can add a UIView and then change the type of the view to any custom UIView - I'm assuming MapView subclasses UIView just as MKMapView does...
I'm trying to create an application that uses the iPhone MapView (under Google Code).
Why not use MapKit? Surely IB comes with an MKMapView in its Library.