How to develop for multiple iOS devices, i.e. multiple storyboards? - xcode

I am currently developing an app for the iPhone 3GS. The deployment target is set to 5.1 and I have created a rich storyboard with lots of segues and scenes. Last night I had the idea that I wanted to make the app available for the iPad, iPhone 4, and iPhone 5. I decided to create a separate storyboard for the different screen sizes / resolutions. Now I am not sure if this is the best practice, as I have just recently started reading about springs and struts on SO, so I don't know much information about it, but for my sake, I just wanted to launch a different storyboard when the application finished launching. However this desired effect is not happening.
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// ViewControllerWelcome *viewControllerWelcome = (ViewControllerWelcome *)[[ViewControllerWelcome alloc]init];
// NSManagedObjectContext *context = (NSManagedObjectContext *) [self managedObjectContext];
// if (!context) {
// NSLog(#"\nCould not create *context for self");
// }
//[viewControllerWelcome setManagedObjectContext:context];
// Do I need to declare my view controllers here?
// Pass the managed object context to the view controller.
CGSize iOSDeviceScreenSize = [[UIScreen mainScreen] bounds].size;
if (iOSDeviceScreenSize.height == 480)
{
// Instantiate a new storyboard object using the storyboard file named iPhoneLegacy
UIStoryboard *iPhoneLegacy = [UIStoryboard storyboardWithName:#"iPhoneLegacy" bundle:nil];
// Instantiate the initial view controller object from the storyboard
UIViewController *ViewControllerWelcome = [iPhoneLegacy instantiateInitialViewController];
// Instantiate a UIWindow object and initialize it with the screen size of the iOS device
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Set the initial view controller to be the root view controller of the window object
self.window.rootViewController = ViewControllerWelcome;
// set the window object to be the key window and show it
[self.window makeKeyAndVisible];
}
if (iOSDeviceScreenSize.height == 968)
{
// Instantiate a new storyboard object using the storyboard file named iPhone4
UIStoryboard *iPhone4 = [UIStoryboard storyboardWithName:#"iPhone4" bundle:nil];
UIViewController *ViewControllerWelcome = [iPhone4 instantiateInitialViewController];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = ViewControllerWelcome;
[self.window makeKeyAndVisible];
}
// iPhone 5 1136 x 640
// iPad Legacy 1024 x 768
return YES;
}
When I try testing to see if the different storyboard file are loading in the Simulator, Simulator just loads the iPhoneLegacy storyboard.
Does this code only work for the physical devices, and do I need separate code for the Simulator?

Fist of all, DELETE YOUR EXTRA STORYBOARDS! You only need one for the iPhone and one for the iPad.
There is a simple way to make a single storyboard for all iPhone/iPod Touch sizes.
Keep only ONE storyboard for the iPhone screen size (including iPhone 5).
Make a #2x file for all of your images.
To switch between 3.5 and 4 inch size, Apple provides a button in the bottom right that looks like a rectangle with arrows pointing in or out - that button will switch between the 3.5 and 4 inch screen sizes.
That's it! No code is actually needed to make a single storyboard for each iPhone/iPod Touch.
For the iPad, you are going to need to create a new storyboard that is made for the iPad and you are going to need to update your UI code to make sure it's compatible with both iPhone and iPad screen sizes. Again, make sure to make #2x image sizes for the iPad as well.

Related

Quartz Composer Screensaver in Xcode

My aim is to bundle a Quartz Composer file into Xcode and build a .saver file. I am currently using the Xcode pre-made template but having problems getting the screensaver to work. I am importing the .qtz file into the project and using QCView to render it on screen, however when I test the built .saver file all I see is a black screen.
- (instancetype)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
{
self = [super initWithFrame:frame isPreview:isPreview];
if (self) {
[self setAnimationTimeInterval:1/30.0];
NSRect viewBounds = [self bounds];
//create the quartz composition view
qcView = [[QCView alloc] initWithFrame: NSMakeRect(0, 0, viewBounds.size.width, viewBounds.size.height)];
//make sure it resizes with the screensaver view
[qcView setAutoresizingMask:(NSViewWidthSizable|NSViewHeightSizable)];
//match its frame rate to your screensaver
[qcView setMaxRenderingFrameRate:30.0f];
//get the location of the quartz composition from the bundle
NSString* compositionPath = [[NSBundle mainBundle] pathForResource:#"QuartzComposerFileName" ofType:#"qtz"];
//load the composition
[qcView loadCompositionFromFile:compositionPath];
//add the quartz composition view
[self addSubview:qcView];
}
return self;
}
I have tried doing the same thing using Xcode 6.4 and it appears that QCView is not working with Xcode 6.4. I put an image in the view and set the size of my QCView to be smaller than the frame. I see a black box for the subview but it never renders. This is true for even the most basic QC composition.
You might be able to get it to work by rendering the composition yourself using a QCRenderer. I gave up before trying this, but here is the documentation: https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/QuartzFramework/Classes/QCRenderer_Class/

Can't figure out how to get UpSide down orientation to work in iOS 8?

I added UIScreen bounds code in AppDelegate.m for iPhone 6 and now device orientation for iPhone 6 won't work for Portrait/ Upside Down. Upside Down is what's not working in simulator and devices.
Code in AppDelegate.m
CGSize iosScreenSize = [[UIScreen mainScreen] bounds].size;
if (iosScreenSize.height == 667) {
UIStoryboard *iPhone6 = [UIStoryboard storyboardWithName:#"iPhone6" bundle:nil];
UIViewController *initialViewController =[iPhone6 instantiateInitialViewController];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen]bounds]];
self.window.rootViewController = initialViewController;
[self.window makeKeyAndVisible];
}
When I included the above code the simulator and device for iPhone 6 doesn't detect Upside Down orientation. I also used nativeBounds and screenBounds, didn't work either for Upside Down orientation.
My code for device orientation...
-(NSUInteger)supportedInterfaceOrientations {
return (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown);
}
I have no idea what to do to get AppDelegate code to let UpSide Down orientation be detected for iPhone 6.
OK, finally figured this one out.
Then realized someone posted the answer here: Setting device orientation in Swift iOS
Go down to DaRk-_-D0G's answer.
I had to subclass my tab bar controller just to override that method. Very obnoxious .

SceneKit animation doesn't play in iOS

I have an example of animations fully working for OS X, start, pause, change speed etc.
Now I started porting this to iOS and can't even start the animation. I simplified the code to a minimum - it works in OS X, but not on iOS.
What I do is
Show a scene with a (animated) character in it (idle animation) - works both in OS X and iOS
Start a run animation on the character. This works on OS X, run animation starts and loops. On iOS the character is positioned at the start of run animation. But it doesn't run...
If I start with e.g. run scene instead of idle, it works - character runs. The problem is when I start an animation (any) after the scene is loaded. It loads the model with the animation but doesn't play.
After detailed comparison between the OS X and iOS version I found 2 differences, which probably are related but I can't figure out how fix them:
In the OS X version the character is not animated until I start an animation. In the iOS version when I attach the nodes from the idle (or whatever other) scene to the root, it's animated. I don't know how to change this.
The OS X version has the scene.dae attached to the Scene View in the Storyboard - this is also the case in iOS. But in iOS for some reason this attachment is not working, ´self.scene´ is nil. This is why I have to instantiate and assign the scene programmatically. I couldn't fix it, tried re-adding the Scene View, assigning the outlet etc.
The scene kit view is added using storyboard. idle and run are .dae files. Each of them contains a full model with the character, and the animation. I just double checked that the animation identifiers are the same as in the .dae files. The models are actually provided in an example from Apple and work perfectly on OS X...
This is the code:
View controller:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)viewDidAppear:(BOOL)animated {
[self.sceneView loadScene];
}
#end
Scene kit view header:
// ASCView.h
#import <UIKit/UIKit.h>
#import <SceneKit/SceneKit.h>
#interface ASCView : SCNView
- (void)loadScene;
#end
Scene kit view implementation:
//
// ASCView.m
// anim_test
//
//
#import "ASCView.h"
#implementation ASCView
- (void)loadScene {
self.backgroundColor = [UIColor blueColor];
self.allowsCameraControl = YES;
[self loadSceneAndAnimations];
}
#pragma mark - Animation loading
- (void)loadSceneAndAnimations {
// Load the character from one of our dae documents, for instance "idle.dae"
NSURL *idleURL = [[NSBundle mainBundle] URLForResource:#"idle" withExtension:#"dae"];
SCNScene *idleScene = [SCNScene sceneWithURL:idleURL options:nil error:nil];
SCNScene *scene = [SCNScene sceneNamed:#"scene.dae"];
self.scene = scene;
NSLog(#"scene: %#", self.scene);
// Merge the loaded scene into our main scene in order to
// place the character in our own scene
for (SCNNode *child in idleScene.rootNode.childNodes)
[self.scene.rootNode addChildNode:child];
// Load and start run animation
// The animation identifier can be found in the Node Properties inspector of the Scene Kit editor integrated into Xcode
[self loadAndStartAnimation:#"run" withIdentifier:#"RunID"];
}
- (void)loadAndStartAnimation:(NSString *)sceneName withIdentifier:(NSString *)animationIdentifier {
NSURL *sceneURL = [[NSBundle mainBundle] URLForResource:sceneName withExtension:#"dae"];
SCNSceneSource *sceneSource = [SCNSceneSource sceneSourceWithURL:sceneURL options:nil];
CAAnimation *animationObject = [sceneSource entryWithIdentifier:animationIdentifier withClass:[CAAnimation class]];
NSLog(#"duration: %f", [animationObject duration]); //0.9
animationObject.duration = 1.0;
animationObject.repeatCount = INFINITY;
[self.scene.rootNode addAnimation:animationObject forKey:#"foofoofoo"];
NSLog(#"animation: %#",[self.scene.rootNode animationForKey: #"foofoofoo"]);
NSLog(#"is paused: %#",[self.scene.rootNode isAnimationForKeyPaused: #"foofoofoo"] ? #"yes" : #"no"); //NO
}
#end
Ohh I found it. In iOS it seems I have to pass the options, in this case:
#{SCNSceneSourceAnimationImportPolicyKey:SCNSceneSourceAnimationImportPolicyPlayRepeatedly}
In OS X I don't - probably the defaults are different.

Resizing tableview height programmatically

I am using storyboard in xcode 4.5.
In my application there is 2 views, view1 and view2.
Each view has a tableview.
A navigation controller is connected to the views.
I am trying to change the height of the tableview depending on the size of the Iphone display.
I am using the following code:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// Verify if the screen is iphone 5
if ([[UIScreen mainScreen] bounds].size.height == 568){
// Resize table hight
CGRect tvframe = [_myTableView frame];
[_myTableView setFrame:CGRectMake(tvframe.origin.x,
tvframe.origin.y,
tvframe.size.width,
tvframe.size.height + 64)];
}
The problem is that the code has no effect when the application starts and view1 appears.
But when I move to view2 and then back to view1 the resizing takes effect.
I don't understand why the reszing doesn't take effect when the application starts.
Have you tried moving the code to ViewWillAppear? That should fix the problem.

iPad: How to display a different screen depending on orientation (landscape / portrait)

I have an iPad application that can be used in all four view modes (portrait up/down and landscape left/right). But at a certain point I have a View that I only want to be seen in landscape mode. So I do the following in the UIViewController that will trigger the action to view the landscape-only view:
- (void) showProperty:(Property *) property {
if ([self interfaceOrientation] == UIInterfaceOrientationLandscapeLeft || [self interfaceOrientation] == UIInterfaceOrientationLandscapeRight) {
PropertyViewController *propertyView = [[PropertyViewController alloc] initWithNibName:#"PropertyViewController" bundle:[NSBundle mainBundle]];
propertyView.property = property;
[self.navigationController pushViewController:propertyView animated:YES];
[propertyView release];
propertyView = nil;
}
else {
RotateDeviceViewController *rotateView = [[RotateDeviceViewController alloc] initWithNibName:#"TabRotate" bundle: [NSBundle mainBundle]];
rotateView.property = property;
[self.navigationController pushViewController:rotateView animated:YES];
[rotateView release];
rotateView = nil;
}
}
This works fine and thus shows either the desired screen (PropertyViewController) when the iPad is held in landscape mode, and if not it shows the RotateDeviceViewController which shows the user a message that he/she is supposed to rotate the device to correctly view the screen.
So when the user then rotates his/her device to landscape mode I want to show them the right view (PropertyViewController). And all of this kinda works!
The problem arises though in this RotateDeviceViewController.. There I have the following:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if (UIInterfaceOrientationIsLandscape(interfaceOrientation))
[self showProperty];
return UIInterfaceOrientationIsPortrait(interfaceOrientation);
}
- (void) showProperty {
PropertyViewController *propertyView = [[PropertyViewController alloc] initWithNibName:#"PropertyViewController" bundle:[NSBundle mainBundle]];
propertyView.property = property;
[self.navigationController pushViewController:propertyView animated:YES];
[propertyView release];
}
So as soon as I rotate the device (when viewing the RotateDeviceViewController) to landscape mode I show the user the PropertyViewController. This works... But when the PropertyViewController appears it shows my layout 90 degrees rotated. So basically it shows the content in portrait mode instead of using the landscape mode (which is actually the way you are holding the device)..
I hope this makes sense and someone can show me what's causing this.
Screenshots to make it more clear:
When device is held in portrait mode
After rotating the device
At this point
- (BOOL)shouldAutorotateToInterfaceOrientation:UIInterfaceOrientation)interfaceOrientation
You are telling the view controller what orientations you support. The device has not actually rotated yet therefore the view controllers intefaceOrientation property will still be portrait so when it is pushed onto the stack it thinks the device is portrait.
pseudo code
shouldAutoRotate... // at this point self.interfaceOrientation == portrait
// you push your controller here so it loads when the property is
I'm not sure if this will work well but the earliest I can see you can push is in
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation

Resources