Short synopsis (XCode 7.2, Swift2, iOS 9.2 as target):
1) In a first.storyboard, I have a single viewController.
2) In a second.storyboard, I have a tabbarController, with multiple navigationControllers with tableviewControllers (see attached image). Also of note is when second.storyboard is the one used on launch, everything works correctly.
3) the main UI for the app is in the first.storyboard, and I want to present the tabbarcontroller in the second.storyboard
4) No matter which way I present it (storyboard reference/segue, presentViewController, showViewController), the tabbarcontroller and all the initial views work, but if I tap a tableviewcell to segue to another view, the whole tabbarcontroller and contents disappear, leaving me back at the viewcontroller in first.storyboard.
I can cheat, and set the rootViewController manually and things seem to work
let sb = UIStoryboard(name: "second", bundle: nil)
let navController = sb.instantiateViewControllerWithIdentifier("secondIdentifier") as! UITabBarController
UIApplication.sharedApplication().keyWindow?.rootViewController = navController
And I suspect I can add an animation to this to not have the transition not be so stark. But this seems like something I shouldn't have to do, and kind of a pain to troubleshoot in the future. Am I missing something fundamental in making this work?
EDIT: Video of it not working https://youtu.be/MIhR4TVd7CY
NOTE: The last app I made originally targeted iOS4, and I did all the views programatically. It seemed like all the updates to IB and segues etc would make life more manageable (and for the most part that has been true), but this is still my first foray in to it, so I may be missing some important points of information to describe the issue.
I have found a superior way to deal with this: UIViewControllerTransitioningDelegate
It's a bit of extra work to implement, but it produces a "more correct" result.
My solution was to make a custom UIStoryboardSegue that will do the animation as well as set the rootViewController.
import UIKit
class changeRootVCSeguePushUp: UIStoryboardSegue {
override func perform() {
let applicationDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let sourceView = self.sourceViewController.view
let destinationView = self.destinationViewController.view
let sourceFrame = sourceView.frame
let destinationStartFrame = CGRect(x: 0, y: sourceFrame.height, width: sourceFrame.width, height: sourceFrame.height)
let destinationEndFrame = CGRect(x: 0, y: 0, width: sourceFrame.width, height: sourceFrame.height)
destinationView.frame = destinationStartFrame
applicationDelegate.window?.insertSubview(self.destinationViewController.view, aboveSubview: self.sourceViewController.view )
UIView.animateWithDuration(0.25, animations: {
destinationView.frame = destinationEndFrame
}, completion: {(finished: Bool) -> Void in
self.sourceViewController.view.removeFromSuperview()
applicationDelegate.window?.rootViewController = self.destinationViewController
})
}
}
I could not find a way in interface builder, or in code other than changing the rootViewController to get this working. I would end up with various random navigation issue like overlapping navigation bars, segue animations not working correctly until I changed tabs, full on lockups with no information in the console, etc.
I have previously presented a tabBarcontroller modally (without changing rootviewController), but everything was done in code (working as of ios7 and objective-c). No clue what is going on under the covers when the view hierarchies are made in a storyboard, but wondering if this is perhaps a bug.
Thanks to multiple other answers here on stackoverflow to get to mine!
Related
I have a question regarding add/remove childviewcontrollers with ContainerView.
I have a UIViewController named "ContainerViewController" that contains a ContainerView, and this ContainerView has a UIViewController named "ContainerLinkViewController". I also have four other UIViewControllers that will be added to or removed from this "ContainerLinkViewController" whenever user a clicks one of the tab button that is placed at the bottom of "ContainerViewController". The storyboard looks like below.
NOTE that the custom segues connected to those four UiViewcontrollers are just empty segue that does nothing but show visual connection in storyboard.
So what I do is first add the First Tab ViewController to "ContainerLinkViewController" as a childViewController by using below code in viewDidLoad().
let firstTabViewController = self.storyboard?.instantiateViewController(withIdentifier: "FirstTabViewController") as! FirstTabViewController
self.addChildViewController(firstTabViewController)
firstTabViewController.view.frame = self.view.bounds
self.view.addSubview(firstTabViewController.view)
firstTabViewController.didMove(toParentViewController: self)
After this initial setup, whenever user tabs on one of those tab buttons, I send signal from "ContainerViewController" that which UIViewController is to be added as new childViewController to "ContainerLinkViewController" and switching the old childViewController and this new childViewController using the below code.
func swapViewControllers(from : UIViewController, to : UIViewController) {
from.willMove(toParentViewController: nil)
self.addChildViewController(to)
to.view.frame = self.view.bounds
self.transition(from: from, to: to, duration: 1.0, options: UIViewAnimationOptions.transitionCrossDissolve, animations: nil) {
finished in
from.view.removeFromSuperview()
from.removeFromParentViewController()
to.didMove(toParentViewController: self)
}
}
Before calling this "swaptViewControllers()", I take the old childViewController by doing the following.
let oldChildViewController = self.childViewControllers.last
My problem is that this "oldChildViewController" is nil when I click one of those tabs for the first time right after the initial setup. So the function "swaptViewControllers()" does not even called because the self.childViewControllers is empty.
I have been struggling with this for few days now and could not find clear answer to solve this. So I decided to ask here to get some help about what might cause this.
Any help?? Thanks.
NOTE : I am using swift 3. Maybe this would help to answer my question since it seems lots of things changed fro swift 2.
first of all sorry for that Question Title you can just read on to know my problem. I am a bit confused about adding multiple libraries that points to a single view controller. So the situation is I have a ViewController where I need a SideMenu and a Drag and Drop Functionality. The ViewController have UICollectionView with Images in it that can be used to drag and Drop in between the images itself and Need a SideMenu that the user can Swipe to navigate to find Menu Items. For SideMenu I have Tried this library SSASideMenu
and for Drag and Drop I used DragDropCollectionView
when I use one library at a time I can make it work but when i use both I get this error at this line
to use SSASidemenu we need some code in AppDelegate File
window = UIWindow(frame: UIScreen.mainScreen().bounds)
// MARK : Setup SSASideMenu
let sideMenu = SSASideMenu(contentViewController: UINavigationController(rootViewController: HomeViewController()), leftMenuViewController: LeftMenuViewController(), rightMenuViewController: RightMenuViewController())
sideMenu.backgroundImage = UIImage(named: "Background.jpg")
sideMenu.configure(SSASideMenu.MenuViewEffect(fade: true, scale: true, scaleBackground: false))
sideMenu.configure(SSASideMenu.ContentViewEffect(alpha: 1.0, scale: 0.7))
sideMenu.configure(SSASideMenu.ContentViewShadow(enabled: true, color: UIColor.blackColor(), opacity: 0.6, radius: 6.0))
sideMenu.delegate = self
window?.rootViewController = sideMenu
window?.makeKeyAndVisible()
inside didFinishLaunchingWithOptions
but when I comment this code in appdelegate the Dragdrop works great.
So my question Is, is it possible to add multiple libraries in a single ViewController ? if so what's going wrong with my method? Can I achieve both the functionality in a same viewcontroller ?
Created a Sample Project with both The Libraries Mentioned in my question. Can get it HERE
Comment the line in Appdelegate and The DragandDropCollectionview works fine and if not error occurs
As explained in DragDropCollectionView example , you must configure your class where you have viewDidLoad (like your picture as showed), otherwise you cannot set delegate:
class ViewController: ...(your class type), DrapDropCollectionViewDelegate {
}
Environment
iOS 9.2
Xcode 7.2
I'm looking to replace the UIWindow's rootViewController with an animation while also removing it from the view hierarchy as well.
class FooViewController: UIViewController
{
}
class LeakedViewController: UIViewController
{
}
Then initiate the transition in the AppDelegate simply by
self.window!.rootViewController = LeakedViewController()
let fooViewController = FooViewController()
self.window!.rootViewController?.presentViewController(fooViewController, animated: true){ unowned let window = self.window!
window.rootViewController = fooViewController
}
Profiling this in Instruments, notice that the rootViewController is still in memory.
Also came across this bug report which seems to suggest the same issue is present in iOS 8.3 and still Open.
Haven't been able to find any references to suggest that as part of the
UIViewController.presentViewController(animated:completion:)
the source view controller is retained (most likely by the UIPresentationController?) or if this is a bug. Notice that the UIPresentationController was first introduced in iOS 8.
If that's by design, is there an option to release the source view controller?
Using a subclass of UIPresentationController with
override func shouldPresentInFullscreen() -> Bool {
return true
}
override func shouldRemovePresentersView() -> Bool {
return true
}
doesn't seem make any difference. Haven't been able to locate anything else in the SDK.
Currently the only way I have found is to use a UIViewController, with a snapshot of what's currently on screen, in place of the root view controller before making the transition.
let fooViewController = FooViewController()
let view = self.window!.snapshotViewAfterScreenUpdates(false)
let viewController = UIViewController()
viewController.view.addSubview(view)
self.window!.rootViewController = viewController
self.window!.rootViewController?.presentViewController(dashboardViewController!, animated: true){ unowned let window = self.window!
window.rootViewController = fooViewController
}
It does work, tho in the console the following warning appears
Unbalanced calls to begin/end appearance transitions for <UIViewController: 0x79d991f0>.
Any ideas on the original question or the warning message appreciated.
Update
I believe I have narrowed it down to this one retain that's missing a release.
That is the possible offending call.
0 UIKit -[UIPresentationController _presentWithAnimationController:interactionController:target:didEndSelector:]
I logged that bug report; I have had no response from Apple engineering on it.
The sample code I submitted with the bug report demonstrating the issue is at https://github.com/adurdin/radr21404408
As far as I am aware, the issue is still present in current versions of iOS, but I have not tested exhaustively. Who knows, perhaps 9.3 beta fixes it? :)
In the application where I encountered this bug, we had been using custom transitions and rootViewController replacement for the majority of screen transitions. I have not found a solution to this leak, and because of reasons could not easily remove all the rootViewController manipulation, so instead worked around the issue by minimising where we used presentViewController and friends, and carefully managing the places where we required it.
One approach that I think has potential to avoid the bug while still retaining similar capabilities to rootViewController swapping--but have not yet implemented--is to have the rootViewController be a custom container view controller that occupies the full screen, and defines a presentation context. Instead of swapping the window's rootViewController, I would swap the single child view controller in this container. And because the container defines the presentation context, the presentations will occur from the container instead of the child being swapped. This should then avoid the leaks.
Inspired by #Jordan Smiths comment my fix ended with a one liner (thanks to the beauty of Swift):
window.rootViewController?.dismissViewControllerAnimated(false, completion: nil)
My complete code to swap the rootViewController with an animation then looks like:
func swapRootViewController(newController: UIViewController) {
if let window = self.window {
window.rootViewController?.dismissViewControllerAnimated(false, completion: nil)
UIView.transitionWithView(window, duration: 0.3, options: .TransitionCrossDissolve, animations: {
window.rootViewController = newController
}, completion: nil)
}
}
With that my memory leak disappeared :-)
The problem is probably that the presented controller and the presenting view controller refer to each other.
I could only get this to work by instantiating two copies of the transitioned-to view controller. One for presenting on the current root and one for replacing the current root after presentation. The copies are easy to achieve for me, since the presented VC's are simple objects. The presented view is left in the window hierarchy after dismissal, so that has to be removed manually after swapping in the new VC.
Here's some Swift.
private func present(_ presented: UIViewController, whenPresentedReplaceBy replaced: #escaping () -> UIViewController)
{
presented.modalTransitionStyle = .crossDissolve
let currentRoot = self.window?.rootViewController
currentRoot?.present(presented, animated: true)
{
let nextRoot = replaced()
self.window?.rootViewController = nextRoot
currentRoot?.dismiss(animated: false) {
currentRoot?.view?.removeFromSuperview()
}
}
}
I want to add Layer SDK to my application (using Swift).
All view controllers here are created programmatically. Therefore I can't segue to them. I have 4 tabs in my application (UITabBarController). One of them is chat. In the chat tab I created a segue to UINavigationController. Now I want to load conversationListViewController in this UINavigationController. For that I created a class for this UINavigationController i.e. ConversationListViewController and added the following code:
class ChatNavigationViewController: UINavigationController {
var conversationListViewController: ConversationListViewController!
var layerClient: LYRClient!
override func viewDidLoad() {
let appDelegate = UIApplication.sharedApplication().delegate as!AppDelegate
self.layerClient = appDelegate.layerClient
self.conversationListViewController = ConversationListViewController(layerClient: appDelegate.layerClient)
self.conversationListViewController.displaysAvatarItem = true
self.navigationController!.pushViewController(self.conversationListViewController, animated: true)
}
}
But this is not working. And giving this kind of effect: the ConversationViewController is not loaded in UINavigationController. I am not sure if I am doing it the correct way. I'm searching for the correct way, but unable to find.
I Solved it. I dragged new NavigationViewController and added ConversationListViewController to rootviewController.I think i should try this first. Anyways thanks guys for your help.
Because you want to do this programatically:
You need to manually initialize the controller before stacking it up on the Navigation Controller. Try this:
navigationController?.pushViewController(self.conversationListViewController.init(), animated: true)
I am just starting to learn how to develop iOS apps. (Note that I am using XCode 6 beta and Swift) I think I'm good with building interfaces themselves, but I seem to be having trouble with segues.
The first page of my app is just a simple screen with a sign up button and a sign in button. I made a separate view controller for a sign up page and put a text field on it. Both of these view controllers are in my storyboard. I then made a custom segue class to simply animate the start page and the sign up page moving one screen to the left, so that the sign up page is now showing. This is my custom segue class:
class SlideFromRightSegue: UIStoryboardSegue {
override func perform() {
let screenWidth = UIScreen.mainScreen().bounds.width
let center = self.sourceViewController.view!.center
UIView.animateWithDuration(0.25,
animations: {
self.sourceViewController.view!.center = CGPoint(x: center.x - screenWidth, y: center.y)
self.destinationViewController.view!.center = CGPoint(x: center.x - screenWidth, y: center.y)
}, completion: { (finished: Bool) in
self.sourceViewController.presentViewController(self.destinationViewController as UIViewController, animated: false, completion: nil)
})
}
}
I based this off a tutorial I found, and I don't think there is a problem here. I simply animate both the source and destination VC's center to be one screen to the left. I'm not entirely sure how animations work in iOS but the tutorial I found was structured similarly, so I assume that the closure passed into the "animations" parameter is called and then iOS generates all of the steps in between and draws each frame to the screen. (correct me if I'm wrong)
So I have a custom segue. I go back to my storyboard, click on the sign up button, go over to the connections tab, and drag the "Triggered Segues - action" over to the second VC, select "custom" and then select my custom class. For some reason, instead of being just "SlideFromRightSegue", it's "_TtC524SlideFromRightSegue". I'm not sure why that's happening, but I assume it has something to do with the fact that I renamed the segue class, and I hope that's not causing any problems. Anyway, doing that creates an arrow from the first VC to the second VC, so I assume it worked.
When I run the app, I get the start screen just like usual. I click the sign up button, and nothing happens. There is probably something I missed, but I put a breakpoint in the perform() function of my segue and another in the prepareForSegue() function of the first VC. Neither one was triggered. I think I have the segue set up properly, I just have something wrong with the actual implementation into my app. Anybody have any idea what's going on?
So yea, it turns out that the problem was that I was using a custom button class as well, which had overrides for the touchesBegan, touchesEnded, and touchesCancelled functions. Those overrides did not callback to the superclass methods, so the action of pressing the button wasn't even being triggered.
I guess the lesson I can take from all this is to make sure to know which function overrides must call back to the superclass, and which should completely override the superclass. Touch event handlers likely need to call back to the superclass.
I don't know why your perform method wasn't called, but it's not due to your code, though the code does have some problems. You never add the destination view controller's view to the window (until the completion where the presentation will do that for you). So, you need to add the view, position it off-screen right, and then have it end up centered, not off screen left like you have in your code,
class SlideFromRightSegue: UIStoryboardSegue {
override func perform() {
let screenWidth = UIScreen.mainScreen().bounds.width
let center = self.sourceViewController.view!.center
let appdel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
appdel.window!.addSubview(self.destinationViewController.view)
self.destinationViewController.view!.center = CGPoint(x: center.x + screenWidth, y: center.y)
UIView.animateWithDuration(0.25,
animations: {
self.sourceViewController.view!.center = CGPoint(x: center.x - screenWidth, y: center.y)
self.destinationViewController.view!.center = center
}, completion: { (finished: Bool) in
self.sourceViewController.presentViewController(self.destinationViewController as UIViewController, animated: false, completion: nil)
self.sourceViewController.view!.removeFromSuperview()
})
}
}