Why does my app rotate without animation? - ios

I have an app with an animated splashscreen and a main interface.
To transition from the splashscreen to the main interface I used this code:
presentViewController(mainViewController, true) {
UIApplication.sharedApplication.keyWindow.rootViewController = mainViewController
}
But apparently this method ruins autorotation (took me a while to find that one). Now I have the issue that this autorotation is not with animation.
I guess this is not with animation because the old viewcontroller still lives below the other viewcontroller and it just forwards the new orientation.
How can I properly transition from one viewcontroller to the next while being able to destroy the old viewcontroller and keep rotation?
Edit: I noticed that my homescreen rotates when I close my app (from its startup orientation to the orientation the app was in when I closed it)

In addition to what Kemal said, I have found my specific issue:
I don't really understand why this gives that problem. I have a storyboard that is set in the Info.plist as the main interface (I ignored this, because I don't need it). In my AppDelegate.finishedLaunching I create a new window and set its root view controller, which I then set as key window. Removing either the window setting in AppDelegate or Info.plist resolves the issue.

According to UIWindow documentation;
If the window has an existing view hierarchy, the old views are
removed before the new ones are installed.
Source Link -> here
So, system automatically destroy while decide to no longer need your first RootViewController. You can handle transition like this;
if var topRootController = UIApplication.sharedApplication().keyWindow?.rootViewController {
while (topRootController.presentedViewController != nil) {
topRootController = topRootController.presentedViewController!
}
topRootController.presentViewController(homeController, animated: true, completion: nil)
}

Related

Add a Launch Screen to the iOS App in Code in Swift (without Storyboard or Launchboard)

I created my views in code. The starting point is the didFinishLaunchWithOptions function in the Appdelegate class.
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
let controller = ViewController() // create an instance of my ViewController and set it to the rootViewController
window?.rootViewController = controller
I would like to add a launch screen that appears instantly when your app starts up. The launch screen should be quickly replaced with the first screen of the app, giving the impression that your app is fast and responsive. I found some answers that said it is not possible(Is there any way to code the LaunchScreen programmatically). My idea was creating a new launchView and replace it through NSTimer. Isn't there any easier or alternative way ?
Thanks
This is not possible as such as the Launch Screen is not a regular View Controller and it cannot hold any logic. Any animations which are there in the App during startup are handled after the launch Screen has appeared and the animations are in the root view controller as in Twitter app. If you are completely against using launch Screens then an alternative would be make a view in IB and use its screenShot as a splashImage. There is no way as of now to make a launchScreen programatically or to add any logic to it.

Xcode 7 - Some segues not working anymore

I upgraded my Swift app to Xcode 7 / Swift 2.0 and now suddenly certain segues in my app no longer work.
I have a segue popping up a "Check In" modal and it works perfectly, but then I have another segue popping up a "Check Out" modal that's near identical and it doesn't launch and the app is frozen.
I re-created the segue from scratch, confirmed that it is identical to the "Check In" one, and it still doesn't work.
I also, instead, tried launching a blank view instead of my Check Out modal and it works fine.
There are no errors, it just freezes, I did confirm that the "prepareForSegue" portion is being called correctly but the "viewDidLoad" portion of my modal is not being invoked.
FYI, I have auto-layout turned off.
Does your "Check Out" modal have a UITextView? If it does, then there's a bug in Xcode 7 / iOS9 where you cannot launch a modal (or any root view) that contains a UITextView if you have set a default text value in storyboard.
A work around is to make sure your UITextView in storyboard is either blank or has the default Lorem Ipsem value and instead, set the text programmatically in code on viewDidLoad.
Hopefully this bug will be fixed soon.
I suspect there is infinite loop somewhere in you "Check Out" controller code. Try pausing app in debugger after presenting controller (when it freezes) and check stacktrace. If it doesn't help, try commenting-out code line-by-line in viewDidLoad and viewWillAppear to find line causing freeze.
I had this problem, try with this
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let viewController:UIViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("Storyboard Id")
self.presentViewController(viewController, animated: true, completion: nil)
})
You just have to give a storyboard id in your view and normally it's working.
My app was working perfectly in iOS8. Its flow was:
VC: View Controller, NVC: Navigation View Controller, ER: Embedded root relationship between NVC and VC, PS: Push Segue, PG: Programmatic presentation
NVC1---(ER)->VC1---(PS)->NVC2---(ER)->VC2 and so on.
The problem was that VC1-(PS)->NVC2 segue did not work, there was no freeze. vc1.prepareForSegue() was executed, but VC2 was not presented. I experimented and found that I did not have the UITextView problem mentioned here.
By following the breadcrumbs outlined below, I got it work after hours of trying in the following way (code at the end):
NVC1---(ER)->VC1---(PG)->VC2
Here are the steps:
As mentioned in Segue issue in iOS9, multiple NVCs are out of style (shame on Apple for suddenly ditching what is actually recommended in your online tutorial and making apps break!). So I modified
NVC1---(ER)->VC1--(PS)->VC2 while VC2 was still embedded in NVC2. I got errors
similar to the StackOverflow post on view not in hierarchy. So I started doing the transition programmatically and after tackling present vs. push ViewController issue that results in "tried to push modally on active view controller" message and then ViewController lifecycle issues that result in "Unbalanced calls to begin/end appearance transactions" messages, I got the following code working. Based on this experience, I really think Apple should have left a working thing alone in Xcode7/iOS9 update.
//*********** VC1.swift. A translation of working code
class VC1:UIViewController{
private var viewController2:VC2?
private var showVC2: Bool = false
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if(showVC2) {
showVC2 = false
self.pushVC2()
}
}//viewWillAppear
private var info:String? // info from incoming user action.
#IBAction unwindToVC1FromUserAction(incomingSegue: UIStoryboardSegue?) {
// Do app-specific stuff to get info from incomingSegue and
// store in self.info variables.
let myboard: UIStoryBoard = self.storyboard!;
self.viewController2 = myboard.instantiateViewControllerWithIdentifier(
"VC2 Storyboard ID") as! VC2
self.prepareVC2() // Pass info to VC2.info, stuff you would have done in prepareForSegue
showVC2= true
} //unwind
private func prepareVC2() {
self.viewController2.info = self.info // etc..
}
private func pushVC2() {
self.navigationController!.pushViewController(viewController2!, animated:false)
}
} //class
I had this, but it was none of the above. My segue call happened to be inside a block. When I moved the call to the main thread I saw that I had a 'NSUnknownKeyException' error in the new View Controller. Being inside the block seemed to prevent the error from registering in Xcode, so the app just appeared to hang without any errors.
Once that was resolved the original code worked fine.

How to properly handle a nil UIApplication.sharedApplication().keyWindow

When the viewDidLoad is called the view is supposed to be loaded.
But I always crash in UIApplication.sharedApplication().keyWindow being nil...
Where should I put my code to have it called once the view is loaded and not everytime the user comes back (I have excluded viewWillAppear for that reason) ?
Situation 1: - Manual UIWindow creation in App's delegate
You have probably somehow added a UIViewController to a UIWindow before setting it as key.
You should call window.makeKeyAndVisible() in your app's delegate after creating the window.
Situation 2: - Automatic storyboard instantiation
The system reads your storyboard, initializes the root view controller, prepares it by calling viewDidLoad and viewWillAppear, adds it to the window and shows the window.
What happens is the system cannot just set the window to the front and animate the view controller onto it, because it's the first view controller and you are not push/popping of a nav controller. And viewDidLoad can take some time... So the Default.png is showing in the meanwhile until the view controller is ready.
Then it calls viewDidAppear, when it has actually appeared. So that's where you should be able to access a keyWindow
Situation 3:
You are in a weird situation where you have windows but none of them is the "key" window currently, but you desperately need to access the window.
Call UIApplication.sharedApplication().windows, see if it's not empty and take the first element. UIApplication.sharedApplication().delegate?.window might have a value too, but it's usually only when the window is key already.
Try using the window property of the application delegate:
UIApplication.sharedApplication().delegate!.window
If you get nil from "UIApplication.shared.keyWindow" at "viewDidLoad", Try wrap your code inside of DispatchQueue.
ex)
override func viewDidLoad() {
DispatchQueue.main.async {
if var window = UIApplication.shared.keyWindow {
// do something
}
}
}

Exception "Application tried to present modally an active controller" crash in iOS 8 only

UIPopoverController *popCtrl = [[UIPopoverController alloc] initWithContentViewController:self.rootViewController.navigationController];
popCtrl.delegate = self;
[popCtrl presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
This code is in a button action, where the button is the "sender".
The line with presentPopoverFromBarButtonItem causes an exception to be thrown with the reason: Application tried to present modally an active controller DetailViewController: 0x15a54c00. DetailViewController is "self" in this case and it is only a delegate to popCtrl, so I don't see how it could be trying to present modally. It's supposed to be presenting rootViewController.navigationController.
As you may have guessed from the names, rootViewController and detailViewController are inside a SplitViewController, but prior to trying to present rootViewController with the the popover, it is removed from the SplitViewController.
This only happens on iOS 8 when built with the iOS 8 SDK. It's also not 100% reproducible. Most of the time this exception occurs, but sometimes after I restart the app it does not occur at all until I rerun the app, then it starts happening all the time again. (I put it in a try/catch so I know it can occur more than once per run.)
I'm almost positive this is yet another iOS 8 bug in the SDK, but has anyone come up with a workaround?
I faced the same problems while updating some app, which was initially developed at the times of iOS 5.0. Removing the controller from the UISplitViewController right before using it in the popover did not work, neither did it help to switch to the newer UIPopoverPresentationController.
However, I was able to swipe-in my (master) controller from the left side. More or less, I discovered that "feature" by accident, so I looked up where this came from and found this in Apple's iOS SDK 5.1 release notes:
In 5.1 the UISplitViewController class adopts the sliding presentation style when presenting the left view (previously only seen in Mail). This style is used when presentation is initiated either by the existing bar button item provided by the delegate methods or by a swipe gesture within the right view. No additional API adoption is required to obtain this behavior, and all existing API, including that of the UIPopoverController instance provided by the delegate, will continue to work as before. If the gesture cannot be supported in your app, set the presentsWithGesture property of your split view controller to NO to disable the gesture. However, disabling the gesture is discouraged because its use preserves a consistent user experience across all applications.
(Source: iOS 5.1 Release Notes, requires Apple Developer Login)
I didn't test what happens if you set the mentioned property to NO and if it releases the controller, but I wouldn't put too much hope on that.
So even after removing it manually from the UISplitViewController, my view controller was still active on that hidden swipeable pane, which appears to happen internally in the SDK. I'm aware of the fact that this still worked fine until iOS 7.x, but I actually consider that as tolerated bug now, closed with iOS 8.0.
I ended up abandoning the popover completely and using the default UISplitViewController behaviour of iOS 5.1 and above. For some extra tweaking, you can change UISplitViewController.preferredDisplayMode to fit your needs, this saved me a lot of time to upgrade old code which never heard of auto layout.
I am using a popover in iOS 8 programmatically in an IBAction. I don't know if this is a bug or not but I do know that they did make some changes to modal views and presentations. There is a good WWDC video on it, see if you can find it. The way I am doing it (keep in mind this is Swift, so you will need to do a little bit of translation) is the following:
let controller = self.settingsVC
controller.preferredContentSize = CGSizeMake(345, 234)
controller.modalPresentationStyle = UIModalPresentationStyle.Popover
var settingsPopController = controller.popoverPresentationController
settingsPopController?.delegate = self
settingsPopController?.sourceView = self.view
settingsPopController?.sourceRect = sender.frame
controller.modalPresentationStyle = UIModalPresentationStyle.Popover
self.presentViewController(controller, animated: true, completion: nil)
In this code, self.settingsVC is a property of the ViewController I set which is initialized to another ViewController in the storyboard, but you can replace controller with the ViewController you need to present as a popover. Also, please note that your UIViewController class must implement UIPopoverPresentationControllerDelegate.
If you need any help with the translation, I'd be happy to give you a hand.

iPad Landscape not resizing SubViews correctly

I am writing an iPad application using MonoTouch, MonoDevelop and Interface Builder, and i've stumbled across a problem that I can't seem to solve. It is probably something simple. What I am trying to do is force an application to Landscapemode from start. The application starts in Landscape, but doesn't resize the subviews correctly.
Basically what I am doing in my appdelegate is that I add a SubView called IndexViewController.xib to Window. In IndexViewController I have a View with a Label on it, if I then in IndexViewController override the ShouldAutorotateToInterfaceOrientation to return this: it works correctly.
public override bool ShouldAutorotateToInterfaceOrientation (UIInterfaceOrientation toInterfaceOrientation)
{
return ((toInterfaceOrientation == UIInterfaceOrientation.LandscapeLeft) || (toInterfaceOrientation == UIInterfaceOrientation.LandscapeRight));
}
However, I now try to create a NavigationController and add it to the IndexController.xib-view on the Initialize:
void Initialize ()
{
this.View.AddSubview (NavController.View);
}
It rotates correctly but doesn't fill the entire "window", I have posted a screenshot and a sample solution. And my info.plist is set to allow "UIInterfaceOrientationLandscapeLeft" and "UIInterfaceOrientationLandscapeLeftRight" as UISupportedInterfaceOrientations
Screenshot
Sample solution here
There is probably something dead simple simple that I am missing, but I can't figure it out.
I'd say this may be due to the fact the way you're building the app's hierarchy isn't the suggested way of doing so. While you could carry on this way, you'll encounter more problems down the road.
Ideally you want to be adding the NavigationController's View the window rather than the having the NavigationController contained within a UIViewController.
In the app delegate, I would do something along the lines of:
IndexViewController indexVC = new IndexViewController()
UINavigationController navController = new UINavigationController(indexVC);
window.AddSubview(navController.View);
This will add the navigation controller's view to the window, with the RootViewController of the NavigationController being your IndexViewController
You should also remove the UINavigationController that is added to your IndexViewController in Initialise().
I just downloaded your sample solution and tried this very quickly and it loaded the app in Landscape with the view fully filling the screen. It didn't auto rotate, but this'll certainly be a good starting point for you :)

Resources