I have a navigationController where I pushViewController two viewcontrollers, OneViewController and TwoViewController. When I rotating the device on the pushed TwoViewController, and then popToViewController (see code) back to OneViewController the view on OneViewController is not updated / rotated.
Update: I have created a small sample project, where I have added a navigationController and two ViewControllers - and then doing the same thing. That is actually working, the only different I can think of is that the Views in my application that fails, is that I load the views from Xib files.
All the constraints in the view is setup in Interface Builder - and saved to the xib file.
I have the following in the view that I reference in Interface Builder as the ViewControllers view:
func loadViewFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
return nib.instantiate(
withOwner: self,
options: nil).first as? UIView
}
override public func awakeFromNib() {
super.awakeFromNib()
// Not setting up any constraints in code of setting any frame here.
}
If I then rotate on the OneViewController it then renders fine.
Do you know why it is not updating when using popToViewController?
let viewControllers: [UIViewController] = self.navigationController!.viewControllers
for aViewController in viewControllers {
if aViewController is OneViewController {
aViewController.view.updateConstraints()
aViewController.view.setNeedsDisplay()
aViewController.view.layoutIfNeeded()
aViewController.view.setNeedsLayout()
self.navigationController!.popToViewController(aViewController, animated: true)
break
}
}
On the view I have also tried this:
override func viewWillShow(passingInAnyObject: AnyObject?) {
self.updateConstraints()
self.setNeedsDisplay()
self.layoutIfNeeded()
self.setNeedsLayout()
}
Call the functionality which you want to update on the viewcontroller to which you pop in viewWillAppear method:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
}
Super strange, but the following solved the problem:
Setting: aViewController.view.frame.size = UIScreen.main.bounds.size
Change the following:
let viewControllers: [UIViewController] = self.navigationController!.viewControllers
for aViewController in viewControllers {
if aViewController is OneViewController {
aViewController.view.updateConstraints()
aViewController.view.setNeedsDisplay()
aViewController.view.layoutIfNeeded()
aViewController.view.setNeedsLayout()
self.navigationController!.popToViewController(aViewController, animated: true)
break
}
}
TO:
let viewControllers: [UIViewController] = self.navigationController!.viewControllers
for aViewController in viewControllers {
if aViewController is OneViewController {
aViewController.view.frame.size = UIScreen.main.bounds.size
self.navigationController!.popToViewController(aViewController, animated: true)
break
}
}
Related
Presenting ViewController Using storyBoard:
if newViewController is in StoryBoard.we can present it using the following method.
let storyboard = UIStoryboard(name: "AViewController"), bundle: UIBundle.main)
let newViewcontroller = storyboard.instantiateInitialViewController() as? AViewController
self.present(newViewcontroller, animated: false, completion: nil)
Is it Possible to present a ViewController which is not in storyBoard but its parent viewcontroller has storyboard
I have create a B viewcontroller programatically (No StoryBoard) ,now I would like to present BViewController and it should use the AViewController StoryBoard?
class AViewController : UIViewController
{
//this class is in storyboard which is generic design that I want to use it in different ways.
}
class BViewController : AViewController
{
....//
}
e.g.
self.present(BViewController(), animated: false, completion: nil)?
when I present BViewcontroller it throws nil for parameters which are from super class.
I founded the answer to reuse the story board file or inherite the story board file.
object_setClass(Sets the class of an object.) will override instance of AViewController with BViewController Class. So on top of AViewController you can add some more methods.
when you have similar viewcontroller with small changes. you have to create different viewcontrollers. using this method you can create on basic viewcontroller with storyboard and reuse that viewcontroller .
class BViewController{
static func vcInstanceFromStoryboard() ->BViewController? {
let storyboard = UIStoryboard(name: "AViewController"), bundle: UIBundle.main)
let instance = storyboard.instantiateInitialViewController() as? AViewController
object_setClass(instance, BViewController.self) //
return (instance as? BViewController)!
}
.....
}
This is an example of how do we use it:
let vc = BViewController.vcInstanceFromStoryboard()
self.present(vc , animation : true)
Your code will work fine if your ViewController truly isn't in a Storyboard. All you have to do is instantiate it without a nibName, like this:
class ViewController: UIViewController {
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
}
Normal Storyboard ViewControllers rely on the coder init to be created, but you can create them normally with the nibName: nil initializer, as long as you don't have any outlets on it (which is your case since the ViewController is not on a storyboard)
Everything is possible programmatically. Not everything is possible through Interface Builder. Storyboards are just a component of Interface Builder.
To present a view controller programmatically from the root:
#objc private func presentViewController() {
guard let root = UIApplication.shared.keyWindow!.rootViewController else {
return
}
let destination = SomeViewController()
destination.transitioningDelegate = YourAnimationVendor()
destination.modalPresentationStyle = .custom
root.present(destination, animated: true, completion: nil)
}
You can omit the transitioning delegate (and the custom animation vendor) and present using a stock UIKit animation, like .fullScreen instead of .custom.
#objc private func presentViewController() {
guard let root = UIApplication.shared.keyWindow!.rootViewController else {
return
}
let destination = SomeViewController()
destination.modalPresentationStyle = .fullScreen
root.present(destination, animated: true, completion: nil)
}
I added 2 tabBar items from storyboard and one UITabBarItem - Menu programmatically. I am successfully able to open the controllers corresponding to tabBarItems which I created using storyboard. However, when I click on "Menu" a blank black screen appears,
#objc public class MainScreenTabsController : UITabBarController {
public override func viewDidLoad() {
super.viewDidLoad()
let tabController = MyViewController()
let tabBarItem = UITabBarItem(title: "Menu", image: UIImage(named: "more-options.png"), selectedImage: UIImage(named: "more-options"))
tabController.tabBarItem = tabBarItem
var array = self.viewControllers
array?.append(tabController)
self.viewControllers = array
}
public func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
return true;
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
}
I followed couple of tutorials for adding tab bar item but all of them had the code I wrote. Am I missing out something very basic?
EDIT:
Class for Menu Controller
#objc public class MyViewController:UIViewController {
public override func viewDidLoad() {
super.viewDidLoad()
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
}
Your app is doing exactly what your code is telling it to do. You are creating an instance of MyViewController and adding it to the UITabBarController's array of View Controllers.
Your MyViewController class file simply defines a blank, black view.
I'm guessing you created a ViewController in your Storyboard that you want to use as MyViewController? If so, you need to instantiate that from the storyboard.
When you're editing your storyboard, assign the MyViewController class to the VC you want to use, and also give it a Storyboard ID - such as MyVC. Then, edit your viewDidLoad function to this:
public override func viewDidLoad() {
super.viewDidLoad()
// wrong way
// let tabController = MyViewController()
if let tabController = storyboard?.instantiateViewController(withIdentifier: "MyVC") as? MyViewController {
let tabBarItem = UITabBarItem(title: "Menu", image: UIImage(named: "more-options.png"), selectedImage: UIImage(named: "more-options"))
tabController.tabBarItem = tabBarItem
var array = self.viewControllers
array?.append(tabController)
self.viewControllers = array
}
}
Since you're creating the ViewController programatically ie. without nib/storyboard, you are responsible for instantiating a UIView object and setting the view property of the view controller. to do that implement the loadView method and assign the view object to view property of the viewController. then you can add custom views to the view object, check the code below.
class MyViewController: UIViewController {
override func loadView() {
// super.loadView() // DO NOT CALL SUPER
//create view
view = UIView()
view.backgroundColor = UIColor.white
//Add a custom view with red color
let customView = UIView()
customView.translatesAutoresizingMaskIntoConstraints = false
customView.backgroundColor = UIColor.red
view.addSubview(customView)
NSLayoutConstraint.activate(
[customView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
customView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
customView.topAnchor.constraint(equalTo: view.topAnchor),
customView.bottomAnchor.constraint(equalTo: view.bottomAnchor)]
)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
It would be good to use Storyboard/Nib for this purpose as you can easily configure custom views/controls using the autolayout in interface builder rather than doing it programmatically. :)
Edit:
if your'e using storyboard then instantiate view controller like given in below code
class MainScreenTabsController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let tabController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MainViewController") as! MainViewController //if using storyboard
let icon = UITabBarItem(title: "Menu", UIImage(named: "more-options.png"), selectedImage: UIImage(named: "more-options")))
tabController.tabBarItem = icon
var controllers = self.viewControllers
controllers?.append(tabController)
self.setViewControllers(controllers!, animated: true)
}
}
When app starts, I use this method to set the root view controller.
func showRootViewController() {
let sb = UIStoryboard.init(name: "Main", bundle: nil)
let mainViewController = sb.instantiateInitialViewController()!
self.window?.rootViewController = mainViewController
self.window?.makeKeyAndVisible()
}
In main view controller, I've code to set the view to container view
class MainViewController: UIViewController {
#IBOutlet weak var headerView: UIView!
#IBOutlet weak var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if(userManager.hasRegistered()) {
let sb = UIStoryboard.init(name: "Login", bundle: nil)
self.setContentViewController(sb.instantiateInitialViewController()!)
} else {
let sb = UIStoryboard.init(name: "Registration", bundle: nil)
self.setContentViewController(sb.instantiateInitialViewController()!)
}
}
func setContentViewController(_ contentViewController: UIViewController) {
print(self.containerView.frame)
let contentView = contentViewController.view!
contentView.frame = self.containerView.bounds
self.containerView.addSubview(contentView)
}
}
Let look at the view, I have container view what is used to add subviews. I set the background to help everyone address the frame of container view
The subview is designed as below:
The result is not as my expected. ContainerView has gone somewhere (I really don't know), the subview is move on the top.
Please help me.
contentView frame is setted in the wrong position.
You should move this line of code:
contentView.frame = self.containerView.bounds
in
override func func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
contentView.frame = self.containerView.bounds
}
You cannot rely on dimensions in viewDidLoad.
Try viewDidLayoutSubviews instead. Be aware that it can be called multiple times
I have solved this type of issue.
i used a tableview with a single row(your subview)to wrap content
I have a ViewController (BViewController) that's inheriting from another UIViewController Subclass (AViewController). (The reason I want to do this is I'm reusing the same view in storyboard 3+ times for different screens.)
When I call:
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "AViewController") as! BViewController
self.show(vc, sender: self)
I get this error:
Could not cast value of type 'test.AViewController' (0x10d08b478) to 'test.BViewController' (0x10d08b3f0).
Here are my subclasses, they have nothing in them.
class AViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
-
class BViewController: AViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
The class in Storyboard is set to AViewController because I'm trying to share IBOutlets across all children without recreating the views. There is only one View Controller Scene in my UIStoryboard.
According to the answer in this thread, it isn't possible to reuse a single UIViewController Scene with multiple subclasses with UIStoryBoard. It is however possible with nib files.
How to use single storyboard uiviewcontroller for multiple subclass
You have to set your view controller's class to BViewController in your storyboard
You probably don't put the right view controller identifier:
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "BViewController") as! BViewController
self.show(vc, sender: self)
(BViewController instead of AViewController)
EDIT:
Here's an example: I have a SignupVC view controller in my storyboard, but its storyboard ID is "signup_vc"
I'm trying to create effect similar to Snapchat in Swift - swiping between UIImagePicker with custom controls and other VCs.
The problem is:
when CameraVC is presented for the first time background is black and swipe between VCs works only on controls (on empty space where should be image from camera it isn't) and warning shows up "Attempt to present UIImagePickerController on CameraVC whose view is not in the window hierarchy!"
when I swipe to another VC and then back to CameraVC UIImagePicker is presented properly and everything works great instead of swiping between VCs which is not working at all. There's also no "window hierarchy" warning
So I think the reason why it's not working is that UIImagePicker is presenting over PageViewController not "in" it, but I have no idea how to fix this.
I'm presenting PageViewController like this:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
var vcPageView = storyboard.instantiateViewControllerWithIdentifier("PageViewID") as! UIViewController
self.presentViewController(vcPageView, animated: false, completion: nil)
}
Loading VCs to table in PageViewController:
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
self.dataSource = self
let storyboard = UIStoryboard(name: "Main", bundle: nil)
var vc0 = storyboard.instantiateViewControllerWithIdentifier("CameraID") as! UIViewController
var vc1 = storyboard.instantiateViewControllerWithIdentifier("vc2ID") as! UIViewController
self.myViewControllers = [vc0, vc1]
self.setViewControllers([myViewControllers[0]], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
And finally CameraVC:
#IBOutlet var cameraOverlay: UIView!
var camera = UIImagePickerController()
override func viewDidLoad() {
super.viewDidLoad()
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera){
self.camera.delegate = self
self.camera.sourceType = UIImagePickerControllerSourceType.Camera;
self.camera.mediaTypes = [kUTTypeImage]
self.camera.allowsEditing = false
self.camera.showsCameraControls = false
self.cameraOverlay.frame = self.camera.cameraOverlayView!.frame
self.cameraOverlay.bringSubviewToFront(self.cameraOverlay)
self.camera.cameraOverlayView = self.cameraOverlay
self.cameraOverlay = nil
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.topMostViewController().presentViewController(self.camera, animated: false, completion: nil)
}
topMostViewController code:
extension UIViewController {
func topMostViewController() -> UIViewController {
// Handling Modal views
if let presentedViewController = self.presentedViewController {
return presentedViewController.topMostViewController()
}
// Handling UIViewController's added as subviews to some other views.
else {
for view in self.view.subviews
{
// Key property which most of us are unaware of / rarely use.
if let subViewController = view.nextResponder() {
if subViewController is UIViewController {
let viewController = subViewController as! UIViewController
return viewController.topMostViewController()
}
}
}
return self
}
}
Try this, In the CameraVC move the camera code in the viewDidLoad() to the viewDidAppear()