Here is my demo project.
I have two view controllers. The main one has the status bar hidden while the second one hasn't.
I created a custom driven transition animation to go from controller one to controller two.
When I'm on the child view controller (the orange one), I start the driven transition by panning from top to bottom. You can see that the status bar is coming back when dragging. And the UIButton "Hello" is moving as well.
I cancel the transition. Then I start it again and you can see the status bar is coming back as well but this time, my button isn't moving, it stays at the same location, as if the status bar was still hidden.
Any idea why it would behave like this once the transition has been cancelled at least once?
(I'm not even talking about the weird thing with the animation that is kind of doubled when cancelling (maybe a bug with the simulator as it doesn't do it on my iphone 6 9.1 and my iphone 5 8.4.)
Add: import Foundation
Then add an outlet:
class ViewController: UIViewController {
#IBOutlet weak var topConstraint: NSLayoutConstraint!
...
}
Then change the value to 0 when the view disappears and then to 20 when it will appear:
override func viewWillAppear(animated: Bool) {
topConstraint.constant = 20.0
}
override func viewWillDisappear(animated: Bool) {
topConstraint.constant = 0.0
}
Full code (make sure to remember to connect the constraint to the outlet):
import UIKit
import Foundation
class ViewController: UIViewController {
#IBOutlet weak var topConstraint: NSLayoutConstraint!
let controllerTransition = InteractiveControllerTransition(gestureType: .Pan)
let controllerTransitionDelegate = ViewController2Transition()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
controllerTransition.delegate = controllerTransitionDelegate
controllerTransition.edge = .Bottom
}
override func viewWillAppear(animated: Bool) {
topConstraint.constant = 20.0
}
override func viewWillDisappear(animated: Bool) {
topConstraint.constant = 0.0
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func unwindToViewController(sender: UIStoryboardSegue) { }
override func prefersStatusBarHidden() -> Bool {
return false
}
#IBAction func helloButtonAction(sender: UIButton) {
// let storyBoard = UIStoryboard(name: "Main", bundle: nil)
// let vc = storyBoard.instantiateViewControllerWithIdentifier("ViewController2") as! ViewController2
//
// vc.transitioningDelegate = controllerTransition
// controllerTransition.toViewController = vc
//
// self.presentViewController(vc, animated: true, completion: nil)
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
// let nvc = storyBoard.instantiateViewControllerWithIdentifier("NavigationViewController2") as! UINavigationController
// let vc = nvc.topViewController as! ViewController2
let vc = storyBoard.instantiateViewControllerWithIdentifier("ViewController2") as! ViewController2
// nvc.transitioningDelegate = controllerTransition
vc.transitioningDelegate = controllerTransition
controllerTransition.toViewController = vc
// self.presentViewController(nvc, animated: true, completion: nil)
self.presentViewController(vc, animated: true, completion: nil)
}
}
Related
I have my main screen with only one button on it "Show next screen". When the second screen(VC) pops up it has 2 buttons (go back and toSelect button).
My goal is to when I show my second screen and select a button on it then go back to first. The button on my second screen will stay selected. How can I do that?
So basically I need to save my actions on the second screen so if I go back to it it will show everything I did.
What is the best way to do it?
Storyboard
The easiest way to achieve this using Delegate and protocol.
you should listen and save the changes of SecondViewController at FirstViewController using delegate methods.
And when you are presenting the secondViewController you will share the saved changes to secondViewController so that button can be selected on behalf of that information
Code -
class FirstViewController: UIViewController {
//secondViewController States
private var isButtonSelected = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func gotoSecondVCAction(_ sender: Any) {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
guard let secondVC = storyBoard.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController else { return }
secondVC.isButtonSelected = isButtonSelected
secondVC.delegate = self
self.present(secondVC, animated: true, completion: nil)
}
}
extension FirstViewController: ButtonSelectionProtocol {
func button(isSelected: Bool) {
isButtonSelected = isSelected
}
}
and for secondViewController
protocol ButtonSelectionProtocol {
func button(isSelected:Bool)
}
class SecondViewController: UIViewController {
var isButtonSelected : Bool = false
var delegate:ButtonSelectionProtocol?
#IBOutlet weak var selectButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
if isButtonSelected {
selectButton.tintColor = .red
selectButton.setTitle("Selected", for: .normal)
}else{
selectButton.tintColor = .green
selectButton.setTitle("Select Me", for: .normal)
}
}
#IBAction func gobackAction(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
#IBAction func selectAction(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
isButtonSelected.toggle()
delegate?.button(isSelected: isButtonSelected)
}
}
I have three viewControllers and two of them connected to a tabBarController. So app should show one of the two vc’s depending Bool value when tapping on tabBar item here is my storyboard
import UIKit
class TabBarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
let userLoggedIn: Bool!
if tabBarController.selectedIndex == 1{
if userLoggedIn == true{
// show firstVC
}else{
// show secondVC
}
}
}
}
You can use childViewController with the parent is ViewController fromStoryboard.
override func viewDidLoad() {
super.viewDidLoad()
///add two ChildVC here, hide 1 ifneeded
}
When click to this tab you check userLoggedIn.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if userLoggedIn == true {
/// show first childVC
/// hide second childVC
} else {
/// hide first childVC
/// show second childVC
}
}
You can check demo: Demo
Simply add .overCurrentContext to modalPresentationStyle in your viewDidLoad like this.
let newVC = self.storyboard?.instantiateViewController(withIdentifier:"secondVC")
newVC?.modalPresentationStyle = .overCurrentContext
self.present(newVC!, animated: false, completion: nil)
Simply set the viewController list when the userLoggedIn variable is modified (assuming userLoggedIn is TabBarCOntroller instance variable) :
class TabBarC: UITabBarController {
#IBOutlet var firstVC : FirstViewController!
#IBOutlet var secondVC : SecondViewController!
#IBOutlet var thirdVC : ThirdViewController!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
for vc in self.viewControllers! {
if let fv = vc as? FirstViewController {
firstVC = fv
} else if let fv = vc as? SecondViewController {
secondVC = fv
} else if let fv = vc as? ThirdViewController {
thirdVC = fv
}
}
}
var userLoggedIn : Bool = false {
didSet {
if userLoggedIn {
self.viewControllers = [firstVC, thirdVC]
} else {
self.viewControllers = [secondVC, thirdVC]
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self. userLoggedIn = false
}
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
if item.tag == 3 { // For purpose of test i set tag for 3 VC to 1 for firsttVC, 2 for secondVC, 3 for thirdVC
// for test : change login value each time thirdVC is selected
userLoggedIn = ! userLoggedIn
}
}
So each time from anywhere in your code you setup userLoggedIn, the tabor show the wanted tab bar items.
The 3 VC are added to the tabbar in the stpryboard.
When selecting thirdVC, the first tab bar item changes between one and two
I'm using the Hero library to do a transition between view controllers.
First view controller:
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var people = [Person]()
override func viewDidLoad() {
super.viewDidLoad()
hero.isEnabled = true
hero.modalAnimationType = .push(direction: .right)
}
#IBAction func handleButton(){
let view = self.storyboard?.instantiateViewController(withIdentifier: "secondVC") as! UIViewController
present(view, animated: true, completion: nil)
}
}
Second view controller:
class Second: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
hero.isEnabled = true
// Do any additional setup after loading the view.
}
#IBAction func handleBackButton(){
hero.modalAnimationType = .push(direction: .left)
hero.dismissViewController()
}
}
It presents the new view without any animation. When I dismiss the second controller, it will apply the correct animation. How do I present the second view with an animation?
Okay found the solution.
In the first case where I have:
#IBAction func handleButton() {
let view = self.storyboard?.instantiateViewController(withIdentifier: "secondVC") as! UIViewController
present(view, animated: true, completion: nil)
}
I have to set the animation there on the view.
#IBAction func handleButton() {
let view = self.storyboard?.instantiateViewController(withIdentifier: "secondVC") as! UIViewController
view.hero.modalAnimationType = .push(direction: .right)
present(view, animated: true, completion: nil)
}
works
Set the modal animation type by setting the Hero extension heroModalAnimationType:
hero.heroModalAnimationType = .push(direction: .right)
Don't forget to disable the default animation for the transition using the Hero.shared object's methods :
func disableDefaultAnimationForNextTransition()
func setDefaultAnimationForNextTransition(_ animation: HeroDefaultAnimationType)
I have 2 controllers inside NavigationController. First pushes the second one to the stack and user can interact with the text field there. Then (in one scenario) user will tap on back button to be taken to the previous screen. Assuming that loading of second one is 'heavy', so I will be keeping only one instance of it once it is needed.
Expected:
I would like to have keyboard hidden once back button is pressed.
Actual:
First responder keeps being restored when I go back to the second for the second time. How to prevent that? Resigning first responder also doesn't do the trick there...
Problem demo:
https://gitlab.com/matrejek/TestApp
Major code parts:
class FirstViewController: UIViewController {
var child: UIViewController = {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let vc = storyboard.instantiateViewController(withIdentifier: "child")
return vc
}()
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func onButtonTap(_ sender: Any) {
self.navigationController?.pushViewController(child, animated: true)
}
}
class SecondViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
view.endEditing(true)
}
}
This does seem odd --- and it seems like your approach should work.
Apparently (based on quick testing), since you are not allowing the Navigation Controller to release the SecondVC, the text field is remaining "active."
If you add this to SecondViewController, it will prevent the keyboard from "auto re-showing" the next time you navigate to the controller - not sure it will be suitable for you, but it will do the job:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DispatchQueue.main.async {
self.view.endEditing(true)
}
}
Edit: Jan 25 2020
Based on new comments, yes, this seems to be a bug.
My previous work-around answer worked -- sort of. The result was the keyboard popping up and then disappearing on subsequent pushes of child.
Following is a better work-around. We have SecondViewController conform to UITextFieldDelegate and add a BOOL class var / property that will prevent the text field from becoming first responder. Comments should be clear:
class FirstViewController: UIViewController {
var child: UIViewController = {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let vc = storyboard.instantiateViewController(withIdentifier: "child")
return vc
}()
#IBAction func onButtonTap(_ sender: Any) {
self.navigationController?.pushViewController(child, animated: true)
}
}
// conform to UITextFieldDelegate
class SecondViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
// bool var to prevent text field re-becoming first responder
// when VC is pushed a second time
var bResist: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// assign text field delegate
textField.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// view has appeared, so allow text field to become first responder
bResist = false
}
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
return !bResist
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// end editing on this view
view.endEditing(true)
// we want to resist becoming first responder on next push
bResist = true
}
}
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()