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.
Related
I am having a weird issue where my app crashes when I am trying to push a new view controller. I have set up a swipe gesture and want to segue to another view controller when a swipe is detected. When I run these 2 lines of code ...
let viewController:ViewController = ViewController()
self.navigationController?.pushViewController(viewController, animated: true)
The app crashes not specifically on either of those lines of code but rather in my ViewController class when in my viewDidLoad method I run this piece of code...
imageView.layer.masksToBounds = true
If I comment that out it crashes when I set the auto-correction type of my textField. What am I doing wrong?
First place I look when the view immediately crashes is in the Outlets for that ViewController in InterfaceBuilder. I look for anything that shows up with an exclamation mark. That usually means I renamed an outlet or broke a connection somehow. Delete anything broken by pressing the little x by the item that's messed up. I'll attach a photo so you can see.
It seems you're loading a ViewController that exists in storyboard with
let viewController:ViewController = ViewController()
which will result in nil outlets , so you have to use
let viewController = storyboard.instantiateViewController(withIdentifier: "VCID") as! ViewController
and give that vc a storyboard identifier like VCID
I think I have solved the issue but it has a weird side effect. Instead of using the line of code in #Sh_Khan's answer, I used ...
let viewController = nav?.storyboard!.instantiateViewController(withIdentifier: "mainVC") as! ViewController
The variable nav is equal to the navigation controller of the current view-controller. This seems to work without any hiccups but for some reason the back button does not disappear from the navigation controller after the segue is preformed. Does anybody know a solution to this, if so leave a comment and I will update my answer.
EDIT:
Another issue is that it wipes everything changed on that ViewController by the user clear. Is there another way to instantiate a ViewController without clearing it?
Follow what #Sh_Khan has said and in addition to that make sure that the view that you are making the push segue from is embedded in a Navigation controller.
Here is what I am trying to do.
When I receive the push notification and I tap I want to show a specific screen in my app. I found a lot about it but I am having trouble due to the complexity of the structure of my application.
Here is how the app is structured:
LoginViewController
RevealViewController (https://github.com/John-Lluch/SWRevealViewController)
UITabbarController
NavigationController
ViewController (This is a table view)
DetailViewContorller
I want to pass some arguments to the DetailViewContorller so I can make sure I get the right results when opening the screen.
Here is the screenshot of my app structure
application Folow
With the following code in my AppDelegate:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tb = storyboard.instantiateViewControllerWithIdentifier("TabBarVC") as! UITabBarController
tb.selectedIndex = 1
window?.rootViewController? = tb
I have managed to get to the tabbar when tapping on the notification but I am not happy with the results. I am still having the following issues:
the revealViewController is nil so I am not able to open my setting panel
I still don't get to the DetailViewController which is at the bottom of my view hierarchy
Any hint will be appreciated.
I had a similar issue although my flow was a bit different. I ended up handling Push Notification event (when user taps on it) and storing the object at the app delegate level. In each view controller that appeared after that (in ViewDidLoad() method) I would check for that object and figure out whether to redirect the flow to the next view controller. To help with this, my notifications had a type associated with them. Unfortunately, I was not able to find a better solution.
P.S. It also appears like you're instantiating View Controllers in code and I was using Storyboards. However, the basic idea is the same. I ended
#MK_Dev, Thanks for your suggestion but I was looking for something easier to manage.
This has actually helped me.
Get top most UIViewController
Here is what I did:
if var topControl = UIApplication.sharedApplication().keyWindow?.rootViewController {
while let presentedContreller = topControl.presentedViewController{
topControl = presentedContreller
}
if topControl.childViewControllers[0].isKindOfClass(MyCustomClassVC) {
// Check if the user is already on the VC we are trying to open
let chatController = topControl.childViewControllers[0] as! MyCustomClassVC
// ... any additional code you need to add here ...
}
}
First of all, I'm brand new to swift and iOS dev! I've decided to get started with it, and so far so good. Except for today.
So I first created a single view app, following the tuto, then I decided to see what happens if a added another view. Using segue to navigate between views seemed to work fine, so I wanted to see how I could navigate programmatically, having in mind that the "destination view" may be conditional on more than just a user pressing a button. So I added a first view to become my initial view controller, and I've found the following code online to transition to my other view:
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let nextViewController = storyBoard.instantiateViewControllerWithIdentifier("home")
self.presentViewController(nextViewController, animated:false, completion:nil)
There was no problem, no error or console message, except for one little problem: some UI elements of the "destination view" (nextViewController in my case) are missing when the view loads! And some of them (but not all) show up after I click on some other elements of the view.. Note that there was/is no problem if I set the nextViewController as the initial one.
I haven't been able to find any clue on the internet about that problem, and I would greatly appreciate it if someone could point me in the right direction..
Oh well, 5 more minutes and finally an error message were enough to fix that problem!
I've found my solution on the following thread:
whose view is not in the window hierarchy
As it's said, moving my code from the viewDidLoad method to the viewDidAppear one fixed the issue. And I had the same error as the OP mentioned.
I've just updated Xxode to work with Swift 2.0. As usual, a lot of new problems showed up.
In my app I have a view controller that checks whether the user is logged in and present either login screen or the app's home screen. It's a pretty simple VC:
class WelcomeViewController : UIViewController {
override func viewDidAppear(animated: Bool) {
if PFUser.currentUser() == nil {
self.performSegueWithIdentifier("segue-require-login", sender: self)
}
else {
self.performSegueWithIdentifier("segue-start-app", sender:self);
}
}
}
That used to work perfectly, but now it doesn't. The segue segue-require-login is of type "Present modally" and it works fine. The segue segue-start-app is "Show (e.g. Push)", but the view never gets pushed, even though the code is being executed (even prepareForSegue is called).
I've tried re-creating the segue, performing a Clean, cleaning the project's build folder but nothing seems to help.
Any thoughts?
There seems to be a bug in iOS 9 and Xcode 7 where if you have a UITextView with placeholder text, it prevents the segue from being triggered.
More explanation in the following answer:
iOS 9 Segue Causes App To Freeze (no crash or error thrown)
To fix it, try removing the placeholder text for the UITextView
I think I have the same problem: I have a UITabelView with cells created from a nib file, when a user tap a cell this method is called:
and when I have the following method prepareForSegue:: the application crashes:
if I delete the line 129 Everything is ok , the method prepareForSegue:: open the right view and the label contactName is shown with its default text.
If I modify the method as follows prepareForSegue:: get exactly what you expect, without having any type of error:
let me know if you also get the same result
I have a simple table view where I handle the select action on the table view. This action follows a segue.
If the segue is a push segue, the next view shows immediately.
If the segue is a modal segue, the next view either:
takes 6 seconds or so to display
shows immediately if I tap again (second tap)
I tried looking around for some ideas, but none seem applicable to my situation. In particular:
I'm performing the segue on the main UI thread
My view is very simple (so there's no issue in viewDidLoad). Plus the fact that it shows up near instantaneous when the segue is push indicates that there is no problem loading the target view
I tried passing nil to the sender; same effect.
Does anyone have any ideas on this?
Trust me and try this. I have run into this problem a few times.
In Swift 2:
dispatch_async(dispatch_get_main_queue(),{
self.performSegue(withIdentifier:mysegueIdentifier,sender: self)
})
or for Swift 3:
DispatchQueue.main.async {
self.performSegue(withIdentifier: mysegueIdentifier,sender: self)
}
As discussed here and here.
It seems (to me...) that this problem happens only when the cell selectionType is not .none.
You may change it to any other option (at the storyboard Attribute inspector, set the Selection field) and it will work fine (working for me...).
The cons is that it messing up the cell UI.
You can call the segue in DispatchQueue.main.async{} block at the didSelect delegate function of UITableViewDelegate as people mention before.
I used the first solution and added at the cell itself -
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(false, animated: false)
}
This will make the cell 'highlight' at the tap, but it will return to its usual UI immediately and it fine for me...
There seem to be various situations when performing a segue will not work properly. For example, if you call performSegue from the action handler of an unwind segue, you will run into various issues, even though you are on the main thread. On my current project, I am calling performSegue from the didSelectRowAt method of a table view. This is one of the most basic segues there is and of course I am on the main thread, yet I was seeing the exact symptoms that the OP described.
I do not know why this happens in some cases and not others, but I have found that deferring the performSegue call using async fixes any potential issues. This used to seem like a hack and made me nervous, but at this point I have several mature projects using this approach and it now seems like the "right" way to do a manual segue.
Here is the Swift 3 version of the code (see the other posts for Swift 2 and Obj-C versions):
DispatchQueue.main.async {
self.performSegue(withIdentifier: "theIdentifier", sender: theSender)
}
The accepted solution worked for me. Code updated for Swift 2.0 below:
dispatch_async(dispatch_get_main_queue(),{
self.performSegueWithIdentifier(mysegueIdentifier, sender:self)
})
Hope help this yo can create programatically modal transition like this in Swift:
let myStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let modalViewController = myStoryboard.instantiateViewControllerWithIdentifier("myModalViewController") as! myModalViewController
modalViewController.modalTransitionStyle = UIModalTransitionStyle.CoverVertical
let navController = UINavigationController(rootViewController: accountManager)
dispatch_async(dispatch_get_main_queue(),{
self.presentViewController(navController, animated: true, completion: nil)
})
For developers organizing their code through subclassing, I've come to quite simple a solution I'd like to share (Swift 4):
import UIKit
class ABCViewController: UIViewController {
// ... Other useful methods like overriding deinit and didReceiveMemoryWarning
// Performs a segue on the main thread to ensure it will
// transition once a UI-slot is available
func performSegueOnMainThread(with identifier: String, sender: Any?) {
DispatchQueue.main.async {
self.performSegue(with: identifier, sender: sender)
}
}
}
Then, simply call it from your implementation:
myViewController.performSegueOnMainThread(with: "ShowDetailsSegue", sender: self)
For me it was the that I had too many "Clear" colored views in my next view, so when the segue was animated, it seemed like it was delaying because of this. I went through the view controller's UI hierarchy looking for clear colors and replacing them with solid black or white, and making sure the alpha is 1 (if it didn't need to be otherwise). My delay is now gone and my modal presentation is smooth.
I tried fixing this multiple ways including moving it to the main thread above. This worked for me:
In storyboard, select the table view in question, (select it in document outline to make sure you've got the right thing. Then in attributes inspector you will be able to see the attributes for the table view as well as the containing scrollview underneath it (all table views are based on scroll view). There is this stupid little box there called "delays content touches". Uncheck it. There is also one "allows cancellable touches" which I'll imagine you want to make sure is unchecked as well, as I think double touches were messing mine up.