I have a UIViewController which is pushed from navigationController that uses custom left/back button with interactivePopGestureRecognizer, however, when I click a row from tableview my app sometimes stucks and after pressing home button and going background then reopening the app, it continues but swipe action of navigationcontroller becomes weird. Here is the video of what am I saying;
https://streamable.com/xvl38
//This is the function that I use to open chat from tableview on didSelectRowAt
func openChat(with nick: String, isFromConversationList: Bool) {
guard let chatVC = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(withIdentifier: "ChatViewController") as? ChatViewController,
let rootNC = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController else {
return
}
rootNC.pushViewController(chatVC, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.interactivePopGestureRecognizer?.delegate = nil
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
}
What can be the reason for that?
Related
I'm using NavigationController to operate the view.
If the value is exceeded on the first view and the value is not satisfied with the 'if' on the second view, I hope 'dismiss' will work.
I have made a similar simple example.
//First View
class ViewController: UIViewController {
#IBAction func goSecond(_ sender: Any) {
let Storyboard = UIStoryboard(name: "Main", bundle: nil)
let DvC = Storyboard.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
self.navigationController?.pushViewController(DvC, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
//Second View
class SecondViewController: UIViewController {
#IBAction func goThird(_ sender: Any) {
let Storyboard = UIStoryboard(name: "Main", bundle: nil)
let DvC = Storyboard.instantiateViewController(withIdentifier: "ThirdViewController") as! ThirdViewController
let num = 1
DvC.num = num
self.navigationController?.pushViewController(DvC, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
//Third View
class ThirdViewController: UIViewController {
var num = Int()
override func viewDidLoad() {
super.viewDidLoad()
print("num: ")
print(num)
if(num != 2) {
print("Success")
// dismiss(animated: true, completion: nil)
// navigationController?.dismiss(animated: true, completion: nil)
} else {
print("Failed")
}
}
}
The third view shows "Success", but the codes below it do not work.
dismiss(animated: true, completion: nil) // not work
navigationController?.dismiss(animated: true, completion: nil) // not work
Pressing the button on the first view moves to the second view, and pressing the button on the second view moves to the third view.
If the value passed when I go to the third view is not satisfied with 'if', I want to go back to the first view.
I want the second and third views to end, not just the screen shift.
Like the finish() of Android.
How can I exit the second and third views and return to the first view?
You are pushing the view controllers onto the stack. dismiss is for dismissing VCs presented modally.
What you want to do is pop the VC to go back one.
navigationController?.popViewController(animated: true)
or to go back to the root view.
navigationController?.popToRootViewController(animated: true)
Hello I am in Viewcontroller B with its NavigationBar, button pressed in Viewcontroller B goes to Viewcontroller C (I don't want NavigationBar in Viewcontroller C)
let vc = self.storyboard?.instantiateViewController(withIdentifier: "viewC") as! ViewcontrollerC
vc.passAction = "saveedit"
vc.passName = passName
self.present(vc, animated: true, completion: nil)
When I click save Button in ViewcontrollerC I should go back to ViewcontrollerB with ViewcontrollerB NavigationBar
let vc = self.storyboard?.instantiateViewController(withIdentifier: "viewB") as! ViewcontrollerB
vc.passName = "\(firstNameTxt.text!)\(" ")\(lastNameTxt.text!)"
self.present(vc, animated: false, completion: nil)
Here the problem is When I go back to ViewcontrollerB I don't see NavigationBar there.
EDITED
class ViewcontrollerB : UpdateDataDelegate {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated);
self.navigationController?.isNavigationBarHidden = false
}
#IBAction func click_edit(_ sender: Any) {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "patientPersonalData") as! patientPersonalDataVC
vc.passName = passName
vc.passAction = "saveedit"
self.navigationController?.pushViewController(vc, animated: true)
}
func loadData() {
}
}
//ViewcontrollerC
protocol UpdateDataDelegate {
func loadData()
}
class Viewcontroller C {
var delegate: UpdateDataDelegate?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.isNavigationBarHidden = true
}
fun click_save() {
self.navigationController?.popViewController(animated: true)
self.delegate?.loadData()
}
}
It seems that you create a new ViewcontrollerB.
Try this to go back to ViewcontrollerB.
self.dismiss(animated: true, completion: nil)
Why don't you simply dismiss ViewcontrollerC on pressing Save button?
In ViewcontrollerC:
var closure: ((String)->())? //Set this closure when you present ViewcontrollerC from ViewcontrollerB
func save()
{
self.dismiss(animated: true) {[weak self] in
self?.closure?("Your_Data")
}
}
This will move you back to ViewControllerB where the navigation bar is already present.
You don't need to hide/show the navigation bar anywhere, neither while presenting ViewControllerC, nor when dismissing it.
Try to present it using navigation controller
let vc = self.storyboard!.instantiateViewController(withIdentifier: "viewC") as! ViewcontrollerC
let navController = UINavigationController(rootViewController: vc)
self.present(navController, animated:true, completion: nil)
and add the following line in your ViewcontrollerC's viewWillAppear method.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.isNavigationBarHidden = true
}
when leave from ViewcontrollerC's viewWillDisAppear method.
override func viewWillDisAppear(_ animated: Bool) {
super.viewWillDisAppear(animated)
self.navigationController?.isNavigationBarHidden = false
}
I just have posted code dedicated on UINavigationBar appearance management on github. check out RRViewControllerExtension, it will solve your problem gracefully.
with RRViewControllerExtension all you have to do is just override method in your viewcontroller.
//override any of the methods below in your viewcontroller's .m file to make specific navigation bar appearance
-(BOOL)prefersNavigationBarHidden;
-(BOOL)prefersNavigationBarTransparent;
-(nullable UIColor *)preferredNavatationBarColor;
-(nullable UIColor *)preferredNavigationItemColor;
-(nullable UIImage *)preferredNavigationBarBackgroundImage;
-(nullable NSDictionary *)preferredNavigationTitleTextAttributes;
Try this .. unhide navigationBar in viewWillAppear of viewControllerC and hide it in viewWillAppear of viewControllerB .
//ViewController C
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.isNavigationBarHidden = true
}
//ViewController B
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated);
self.navigationController?.isNavigationBarHidden = false
}
try this then
//ViewController C
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
//ViewController B
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated);
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
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)
}
}
As SplitViewController loads, I am showing a Login Screen. On successful login, I need to go back to parent view controller. Somehow dismissal is not working for me. Here is the code:
ParentViewController:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if !appDelegate.loggedIn {
self.performSegueWithIdentifier("loginScreen", sender: self)
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
Child ViewController:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.loggedIn = true
self.dismissViewControllerAnimated(true, completion: nil)
The dismissal part never works. It just hangs on Login Screen.
Try one of the following:
1) remove self. keep only dismissViewControllerAnimated(true, completion: nil)
or remove self. and make it:
2) presentingViewController.dismissViewControllerAnimated(true, completion: nil)
or remove self. and try:
3) presentedViewController.dismissViewControllerAnimated(true, completion: nil)
Try this in your parent view controller:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if !appDelegate.loggedIn {
let loginVC: UIViewController = self.storyboard!.instantiateViewControllerWithIdentifier("LoginViewController") as UIViewController
loginVC = UIModalTransitionStyle.CoverVertical
self.parentViewController?.presentViewController(loginVC, animated: true, completion: nil)
}
}
You're instantiating the new view controller by its own name rather than by the segue name.
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()