I am presenting a view controller modally in iOS app. The issue is that there is no crash and the app freezes as soon as presentViewController:animated is called. The stats show the CPU usage to be 100% and the usage doesn't go down even after manually closing the app.
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
CustomModalViewController *vvc = [sb instantiateViewControllerWithIdentifier:#"CustomModalViewController"];
if(!vvc){
NSLog(#"ERROR!!! vvc is null");
}
NSLog(#"instantiate modal view controller");
vvc.providesPresentationContextTransitionStyle = YES;
vvc.definesPresentationContext = YES;
vvc.data = data;
NSLog(#"before presenting modal view controller");
[vvc setModalPresentationStyle:UIModalPresentationOverCurrentContext];
[self presentViewController:vvc animated:YES completion:nil];
I tried printing some debug statements in the viewDidLoad of my custom class, but those are also not getting called.
I don't understand why the view controller is not being displayed. Any help will be appreciated. I want to know in what case does your app go into infinite loop on pushing a view controller or is it because of some other cause??
UPDATE:
This error occured after I updated to XCode 7. Not sure, but I guess this might be an issue with new SDK- the UIKit or LLVM compiler. I copied my project to another mac with Xcode 6.4 and the error disappears!!! I haven't changed any build settings either that would cause the issue.
Any pointers on how to proceed?
Ok, this is bizarre, but I hope it helps: I have the EXACT same issue, the CPU jumps to 100% and view never shows. Works perfectly well in Xcode 6.4. In Xcode 7.1, on the view that I am calling, I have got a UITextView with some placeholder text "Notes:". What I found is that if I clear out the placeholder text, the view LOADS and all of the procedures execute as normal. If the placeholder text length is greater than 9 characters, the view also loads and procedures execute as normal. If the placeholder text length >0 and < 10, it's a no go. No view and CPU at 100%. This is odd, I realize, but hopefully it helps you out. Like you, I've got no errors or console output to show, it just spins.
EDIT FYI, blanking out the placeholder text in the storyboard, and just setting it in code fixes this as well irrespective of length
Same issue here.
I have a UITextView with the default string " Text " and fails with 100% CPU load. I was putting the placeholder only in code but until I remove the " Text " from the storyboard didn't work.
My code was developed in Xcode 6 and I noticed this error after send a new app revision builded with xcode 7.
I had the same exact problem as described in the OP and the answers:
Present a view controller (in my case a PageViewController)
The presented view controller has an button that when pressed presents another view controller to add a note
Move the app to the background while the "note" view controller is being presented. Then bring it back to the foreground.
The app is now frozen. When connected to the debugger, it seems to be frozen indefinitely, but when running in prod it crashes. The CPU was pegged at 100% and the memory usage was climbing.
I ultimately tracked this down to my "Note" view being set as the First Responder, When my view appears, via override func viewWillAppear(_ animated: Bool), I set the note UITextView to become the first responder, noteTextView.becomeFirstResponder().
If I removed that line and never tapped the note field, my app would successfully resume from the background.
To fix this problem, I ended up adding observers for when the app moves to the background and moves into the foreground. In these observers, I make the note text view become and resign firstResponder, respectively.
override func viewDidLoad() {
super.viewDidLoad()
//set up your view controller here.
NotificationCenter.default.addObserver(self, selector: #selector(self.appMovedToForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.appMovedToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
}
#objc func appMovedToForeground() {
self.noteTextView.becomeFirstResponder()
}
#objc func appMovedToBackground() {
self.noteTextView.resignFirstResponder()
}
I'm not sure if I should be doing something differently to prevent this problem, or if it's best practice to resign first responder when the app goes into the background, but this seems to work for me.
I was presenting settings screen, so
I converted this:
private lazy var settingsVC: SettingsVC = {
let controller = SettingsVC.fromStoryboard()
settingsVC.delegate = self
return controller
}()
To this:
private lazy var settingsVC = SettingsVC.fromStoryboard()
And set the delegate later in view did load on controller which was presenting the settings:
override func viewDidLoad() {
super.viewDidLoad()
settingsVC.delegate = self
}
And now it works
Related
Strange things seem to happen when using the new iOS 11 navigationItem.searchController method on a detail view of a UISplitViewController.
The searchBar partly appears as a blank space on the first presentation, then appears in the wrong UITableViewController, but corrects itself after a few push and pops of UITableViewController.
I used to put the searchBar in the tableHeaderView, but I changed the code according to the WWDC recommendation:
if (#available(iOS 11.0, *)) {
self.navigationItem.searchController = self.searchController;
self.navigationItem.hidesSearchBarWhenScrolling = NO;
} else {
self.tableView.tableHeaderView = self.searchController.searchBar;
}
This example is using standard sample code (default project for UISplitViewController and the Apple demo of UISearchController updated for iOS 11 (using a single UITableViewController)).
The initial view containing the searchController looks like this:
And clicking a UITableView item yields this:
However after clicking on a UITableView item and returning twice - it looks as it should:
and:
I was trying to determine why the Apple example for UISearchController worked and my code didn't. The main difference was it was embedded in UISplitViewController in the Detail View. Which means if shown in Compact mode has an extra UINavigationController in the stack. I found if my seque avoided the extra UINavigationController - it works correctly (but breaks device rotation). Similarly change the segue to modal allows it to work.
I note this is similar to this old question: UISplitViewController with new UISearchController issue with UISearchBar
I have created a sample project that demonstrates the problem (sample code: searchControllerDemo)
I'm stumped as to what is going on. So any help would be very much appreciated.
It's been a while since this erupted but thought to leave a note here for whoever will face the same issue...
On compact width devices, upon segueing from master to detail, the detail navigation controller is on top of the master view controller, unlike regular width where the two navigation controllers have their own separate root view controllers.
So, the UINavigationController of the detail view controller needs to be removed upon segue in combact width devices using UISplitViewControllerDelegate method: splitViewController(_:showDetail:sender:)
func splitViewController(_ splitViewController: UISplitViewController, showDetail vc: UIViewController, sender: Any?) -> Bool {
if splitViewController.isCollapsed, let navController = vc as? UINavigationController {
if let detailVC = navController.topViewController {
splitViewController.showDetailViewController(detailVC, sender: sender)
return true
}
}
return false
}
What I want to achieve: segue from gameVC to mainmenuVC and get rid of the gameVC
When the app starts it first shows a main menu viewcontroller with a play button that segues to the gameviewcontroller. When the user taps on a menu button sprite the following function in the gameviewcontroller gets called and it segues back to the main menu:
func returnToMainMenu () {
//This works but does not deinit the vc
navController?.dismissViewControllerAnimated(true, completion: nil)
/* this does not do anything:
navController?.popViewControllerAnimated(true)
*/
}
This is probably not how it is done properly and I think that might be the problem, but I could not get to work otherwise because gameViewController.navigationViewController is nil.
This is how my storyboard looks:
This is how the memory usage looks when the app is running. Those spikes/steps occur whenever the gameviewcontroller is loaded. It seems to me that the problem is, that the gameviewcontroller does not de-initialize when returnToMainMenu() is called.
Also, this never gets executed:
deinit {
debugPrintln("GameViewController deinitialized")
}
update:
I deleted this
navController = self
and defined navController in returnToMainMenu like this:
let navController = view.window?.rootViewController as! UINavigationController
segue back to main menu still works but it still does not deinit the vc
Of course it will memory leak.
override func viewDidLoad() {
navController = self
}
You just gave yourself a reference to itself. Usually when your vc goes offscreen, the view hierarchy no longer holds the view so the view is deinited. You set a reference to itself so whatever you do, it will always hold itself in memory and will never deinit.
I figured it out after watching Lecture 8 of the stanford iOS8 course (at 14:23).
The problem was that I added a reference to the gameviewcontroller in my gamescene to call its returnToMainMenu() method from the scene. In order for its memory to be cleared all the references to the VC have to be set to nil.
I solved it by referring to the navigation controller directly from my scene like this:
(scene!.view!.window?.rootViewController as! UINavigationController).dismissViewControllerAnimated(false, completion: nil)
I have this application which uses internally a UISplitViewControler to display the main interface. The problem I have is that on IOS7 I don't see the button on the left to open the master panel.
The theory says that I have to set the delegate and the button will appear. In practice - my delegate is not called in IOS7. It does on IOS8.
First try:
I am following the normal double navigation controller scheme (described here: http://whoisryannystrom.com/2014/11/17/UISplitViewController-iOS-7/)
Code is swift :)
As I need my app to work on IOS7 phones, in am not creating the split view controller in code, but using the one in the storyboard:
(somewhere in app delegate):
UIStoryboard *board = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
UIViewController *newController = [board instantiateViewControllerWithIdentifier:#"LoginViewController2"];
self.window.rootViewController = newController;
The delegate is created in the master, and assigned to master. This works on IOS8.
Code in the master
override func akaweFromNib() {
super.awakeFromNib()
if let splitViewController = self.splitViewController {
let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as UINavigationController
if (splitViewController.respondsToSelector(Selector("displayModeButtonItem"))) {
navigationController.topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem()
}
splitViewController.delegate = self
}
}
This works, but I have to open the drawer and choose something on the master view (create a new segue) in order to see the button.
Second try
As this did not work - I created a new UISplitViewController and set the split view controller on the storyboard to this new class. Move the onWakeFromNib to this new class (and set the delegate as before). New code works on IOS8, but under IOS7 (at least on the IPad Emulator) the new class is not used for the split view controller - I don't hit a breakpoint in the new code.
What am I doing wrong?
Edit:
While copying code here, I forgot to mention that I am doing:
navigationItem.leftItemsSupplementBackButton = true
navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem()
But - this is only available in IOS8. What can I do in IOS7?
2015-02-12 10:37:55.987 OlympiaTracking[92551:607] -[UISplitViewController displayModeButtonItem]: unrecognized selector sent to instance 0x7b67f1c0
Edit 2:
I also followed ios7 no displayModeButtonItem or targetDisplayModeForActionInSplitViewController which works, but only after the first segue. When the controller is first displayed, the button is not visible.
Open this link and move to the iPad part. Where it says
Notice that when the iPad app is first opened up, there is no indication that this is a split view controller at all! To trigger the Master view controller, the user has to magically know to swipe left to right.
Even when the navigation controller is in place, the UI is not that
much better at first glance (although seeing a title is definitely an
improvement):
My application has a setup screen that should be presented modally on the root view controller if certain conditions are met.
I have looked around on SO and the internet and the closest answer so far to how to go about doing this is here:
AppDelegate, rootViewController and presentViewController
There are 2 problems with this approach however:
In iOS 8, doing it this way makes a log appear in the console, which doesn't seem to be an error, but is probably not good nonetheless:
Unbalanced calls to begin/end appearance transitions for UITabBarController: 0x7fe20058d570.
The root view controller actually shows up very briefly when the app launches, and then fades into the presented view controller (even though I explicitly call animated:NO on my presentViewController method).
I understand that I can set my root controller dynamically in applicationDidFinishLaunchingWithOptions: but I specifically want to present the setup screen modally, so that when the user is done with it, it dismisses and the true first view of the application is revealed. This is to say, I don't want to dynamically change my root view controller to my setup screen, and present my app experience modally when the user is done setting up.
Presenting the view controller on my root view controller viewDidLoad method also leads to a noticeable blink of the UI when the app is launched for the first time.
Is it possible to programmatically present a view controller modally, before the application has rendered anything so that the first view in place is the modal view controller?
UPDATE: Thank you for the comments, adding my current code as suggested:
In my AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
[self.window makeKeyAndVisible];
[self.window.rootViewController presentViewController:[storyboard instantiateViewControllerWithIdentifier:#"setupViewController"] animated:NO completion:NULL];
return YES;
}
This does what I need except for the fact that it briefly shows the window's root view controller for a second when the application launches, then fades the setupViewController, which I find odd given that I am presenting it without animation and fading is not how a modal view controller is presented anyway.
The only thing that has gotten me close is manually adding the view in the root view controller's view did load method like so:
- (void)viewDidLoad
{
[self.view addSubview:setupViewController.view];
[self addChildViewController:setupViewController];
}
The problem with this approach is that I can no longer "natively" dismiss the setupViewController, and will now need to deal with the view hierarchy and animated it out myself, which is fine if it's the only solution, but I was hoping there was a sanctioned way of adding a view controller modally without animation before the root view controller displays.
UPDATE 2: After trying a lot of things out and waiting for an answer for 2 months, this question proposes the most creative solution:
iOS Present modal view controller on startup without flash
I guess it's time to accept that it's just not possible to present a view modally without animation before the root view controller appears. However the suggestion in that thread is to create an instance of your Launch Screen and leave that on for longer than default until the modal view controller has had a chance to present itself.
I guess it's time to accept that it's just not possible to present a view modally without animation before the root view controller appears.
Before it appears, no, you can't present. But there are multiple valid approaches to solve this visually. I recommend solution A below for its simplicity.
A. add launchScreen as subview, then present, then remove launchscreen
Solution is presented here by ullstrm and does not suffer from Unbalanced calls to begin/end appearance transitions:
let launchScreenView = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()!.view!
launchScreenView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
launchScreenView.frame = window!.rootViewController!.view.bounds
window?.rootViewController?.view.addSubview(launchScreenView)
window?.makeKeyAndVisible()
// avoiding: Unbalanced calls to begin/end appearance transitions.
DispatchQueue.global().async {
DispatchQueue.main.async {
self.window?.rootViewController?.present(myViewControllerToPresent, animated: false, completion: {
launchScreenView.removeFromSuperview()
})
}
}
B. addChildViewController first, then remove, then present
Solution is presented here by Benedict Cohen.
I was in the same boat as you and found the same answer. I learned that you can get rid of the first problem (unbalanced calls warning) by setting the modalPresentationStyle of your setupViewController to .OverCurrentContext or .OverFullScreen. Problem solved - so I thought.
Only later I noticed the second problem and that was something I couldn't live with... back to square one.
As you, I wanted a solution with a normal view hierarchy and I didn't want to 'fake' something. I think the most elegant solution is switching your windows rootViewController on first dismissal of your setupViewController.
So, at launch, you set the setupViewController as the rootViewController (if needed):
var window: UIWindow?
var tabBarController: UITabBarController!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
tabBarController = window!.rootViewController as! UITabBarController
if needsToShowSetup() {
let setupViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("SetupViewController") as! SetupViewController
window?.rootViewController = setupViewController
}
return true
}
When setup is done you call a method in your appDelegate to switch to the 'real' rootViewController:
func switchToTabBarController() {
let setupUpViewController = window!.rootViewController!
tabBarController.view.frame = window!.bounds
window!.insertSubview(tabBarController.view, atIndex: 0)
let height = setupUpViewController.view.bounds.size.height
UIView.animateWithDuration(0.5, delay: 0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1, options: .allZeros, animations: { () -> Void in
setupUpViewController.view.transform = CGAffineTransformMakeTranslation(0, height)
}) { (completed) -> Void in
self.window!.rootViewController = self.tabBarController
}
}
I was after a 'cover vertical' dismiss animation. For crossfade and others, you could use UIView.transitionFromView(fromView: UIView, toView: UIView...). Hereafter you can present/dismiss your setupController the normal way, so your doneButton action could be something like this:
#IBAction func doneButtonSelected(sender: UIButton) {
if presentingViewController != nil {
presentingViewController!.dismissViewControllerAnimated(true, completion: nil)
} else {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.switchToTabBarController()
}
}
Actually, I implemented this through delegation with the appDelegate being the delegate the first time around.
I understand that I can set my root controller dynamically in applicationDidFinishLaunchingWithOptions: but I specifically want to present the setup screen modally, so that when the user is done with it, it dismisses and the true first view of the application is revealed.
I have two suggestions. One is to try doing this in viewDidAppear:. I tried it and although you do see the root view controller's view if you look carefully, you barely see it, and sometimes you don't see it at all if you blink:
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self presentViewController:[self.storyboard instantiateViewControllerWithIdentifier:#"setupViewController"] animated:NO completion:NULL];
}
Of course you'd need to add a flag so that you don't do that every time viewDidAppear: is called - otherwise you'll never be able to get back to this view controller at all! But that's trivial and I leave it as an exercise for the reader.
My other suggestion - and you have clearly thought about doing this - is to use a custom embedded (child) view controller instead. That works around the limitations of the whole "presentation" thing.
I'd launch and set up things dynamically, as you say, with the child view controller present if needed, configuring it all during the launch process. The child view controller's view would just cover the root view controller's view. So that's what the user would see as the app launches.
And then when the user's setup procedure is over and the user "dismisses" this view, you tear that view down, with animation, and remove the child view controller - revealing the root view controller's view underneath. The animation will make this all indistinguishable from the dismissal of presented view, even though it isn't really one.
wi have 2 questions about using a popover in my application. First is about the popoverControllerDidDismissPopover function, it is never called in my application. I checked here about the same topics and found out that normally the problem is that the delegate is not set. But in my case i had:
class MainTableViewController: ...,UIPopoverControllerDelegate {
And to call the modal:
var popover: UIPopoverController!
On my cell tap event
popover = UIPopoverController(contentViewController: popoverContent)
popover.delegate = self
popover.presentPopoverFromRect(currentCell.LabelCellTitle.frame, inView: currentCell.LabelCellTitle.superview, permittedArrowDirections: UIPopoverArrowDirection.Left, animated: true)
The popover appears, but if i tap outside of it the method:
popoverControllerDidDismissPopover
will not be called. Any ideas?
The second question is about an error ill get when i tap on my cell again to load up the popover.
Warning: Attempt to present <...24ModalTableViewController: 0x7fda8a55e440> on <...23MainTableViewController: 0x7fda8a62eb80> which is already presenting (null)
Ok, i guess that mean that the ModalTableViewController is still there. Do i need it to set to nil with the popoverControllerDidDismissPopover function? Or how do i solve this warning?
Thanks in advance.
Edit:
After cleaning my projekt problem 1 is now solved (little mysterious) - maybe a bug in XCode Beta. Problem 2 still present
Edit2:
If i now set the Content and the Popover to nil after dismiss, i got the following error:
func popoverControllerDidDismissPopover(popoverController: UIPopoverController!) {
self.popover = nil
self.popoverContent = nil
}
Application tried to represent an active popover presentation
Why is the popover still active?