Double transition bug tvOS - swift

I have transition bug on tvOS platform: When I push "enter" button on remote control twice I have double transition to next view controller. I've fixed this problem on ios like this:
class ViewController: UIViewController, UINavigationControllerDelegate {
var segueInProgress: Bool?
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.delegate = self
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
return segueInProgress == false
}
func navigateShow(to viewController: UIViewController, sender: Any? = self) {
if self.segueInProgress == true { return }
if let navigationController = self.navigationController {
navigationController.show(viewController, sender: sender)
} else {
self.show(viewController, sender: sender)
}
}
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
// Or like this: self.view.isUserInteractionEnabled = false
self.segueInProgress = true
}
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
self.segueInProgress = false
// Or like this: self.view.isUserInteractionEnabled = true
}
}
But on tvOS this solution doesn't work and I don't know why. When I make double click to fast I have several transitions. Please help
let detailVC: DetailViewController = DetailViewController.instantiateViewController()
navigateShow(to: detailVC)

You could disable the button after the press which would prevent the user from double pressing and just make sure that if you need to worry about state coming back to your ViewController you enable it back on viewWillAppear.

Related

do something after first view controller appears

I have a View Controller with a button..
This button calls a "Present as Popover Seague" to a second view controller.
The second view controller has a close button with this function:
#IBAction func exit(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
Now I would like to do something in the first controller, after the second Controller is dismissed.
In the first view controller I tried this functions:
override func viewDidAppear(_ animated: Bool) {
print("viewDidAppear")
}
override func viewWillAppear(_ animated: Bool) {
print("viewWillAppear")
}
but no console log will shown.
Where is my mistake?
FirstViewContorller
import UIKit
class firstVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//ERROR
secondVC.dismissCompletion = {
print("dismissCompletion")
}
}
}
SecondVC (popover)
import UIKit
class secondVC: UIViewController {
var dismissCompletion: (() -> Void)?
// EXIT POPOVER
#IBAction func exit(_ sender: UIButton) {
self.dismiss(animated: true, completion: dismissCompletion)
}
}
The viewDidAppear() method of the main view controller won't be called because of the popover presentation style you use. If you choose to present the second view controller full screen - those methods will fire.
If we're sticking with the popover, the first thing you need to do is in your second view controller, the one that's being presented, add a property for a closure that will be executed upon its dismiss:
class PopoverViewController: UIViewContoller {
var dismissCompletion: (() -> Void)?
#IBAction func exit(_ sender: UIButton) {
self.dismiss(animated: true, completion: dismissCompletion)
}
}
And in your main view controller you define what needs to be done upon the popover's dismiss:
popoverViewContoller.dismissCompletion = {
// do stuff
}
UPDATE:
I assume you've setup the segue in your storyboard. I also assume that in your storyboard you've given the view controllers their respective class names:
This is what your code should look like:
class FirstViewController: UIViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let secondVC = segue.destination as? SecondViewController else {
return
}
secondVC.dismissCompletion = {
print("Popover dismissed")
}
}
}
class SecondViewController: UIViewController {
var dismissCompletion: (() -> Void)?
#IBAction func exit(_ sender: Any) {
dismiss(animated: true, completion: dismissCompletion)
}
}
Please note the classes naming and the way I got the secondVC instance.

iOS 13 - Right Bar Button Item goes offset in Modal Sheet presentation

I have this strange problem with iOS 13 and its new sheet cards style modal presentation.
From ViewController1 I modal present ViewController2 embedded in a NavigationController, and everything works fine.
From ViewController2, I then modal present ViewController3 embedded in a NavigationController, and I get the Right Bar Button offset.
Here is a video of the problem: does anybody have a fix?
Main View Controller
import UIKit
let vc1identifier = "vc1identifier"
let vc2identifier = "vc2identifier"
class ViewController: UIViewController {
#IBAction func tap1(_ sender: UIButton) {
if let navigation = self.storyboard?.instantiateViewController(withIdentifier: vc1identifier) as? UINavigationController,
let nextVC = navigation.contentViewController as? UnoViewController {
//self.navigationController?.pushViewController(nextVC, animated: true)
self.present(navigation, animated: true, completion: nil)
}
}
}
extension UIViewController {
var contentViewController: UIViewController {
if let navcon = self as? UINavigationController {
return navcon.visibleViewController!
} else {
return self
}
}
}
Second View Controller
import UIKit
class UnoViewController: UIViewController {
#IBOutlet weak var barButton: UIBarButtonItem!
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
barButton.title = "GotoVC2"
}
#IBAction func pressThis(_ sender: UIBarButtonItem) {
if let navigation = self.storyboard?.instantiateViewController(withIdentifier: vc2identifier) as? UINavigationController,
let nextVC = navigation.contentViewController as? DueViewController {
self.present(navigation, animated: true, completion: nil)
}
}
}
I came across the same issue.
Solution is easy, you just need to tell navigationbar it needs layout like this
override public func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if #available(iOS 13.0, *) {
navigationController?.navigationBar.setNeedsLayout()
}
}

How do I pass data from a UIViewController to UITabBarController?

In UIViewController:
class ViewController2: UIViewController {
var points = 0
var pressed = false
#IBOutlet weak var label: UILabel!
#IBAction func slider(_ sender: UISlider) {
number = Int(sender.value)
label.text = String(number)
}
#IBAction func submitbutton(_ sender: UIButton) {
pressed = true
}
}
I am trying to do something in a TabBarController if a button in a UIViewController is pressed and also add a number to the number in another TabBarConroller.
Image 1: This shows the connection between my ViewControllers.
Image 2: This shows the first two ViewControllers.)
Image 3: This shows the third and fourth ViewController
Here is my storyboard. I've put a few words to describe what I am trying to do in the images. Please tell me if you need a clearer description. Thank you!
If the ViewController is a child of the UITabBarController that you want to access, you can simply use tabBarController property of the UIViewController, e.g., use this to change selected controller to the first one:
#IBAction func submitbutton(_ sender: UIButton) {
pressed = true
self.tabBarController?.selectedIndex = 0
}
So let's say that you have a custom UITabBarController subclass, e.g.:
class CustomTabBarController: UITabBarController {
func acceptData(points: Int) {
print(">>> Accepted: \(points)")
// or do anything you need to do with it
}
}
Then you can pass it data as follows:
#IBAction func submitbutton(_ sender: UIButton) {
pressed = true
if let customTabController = self.tabBarController as? CustomTabBarController {
customTabController.acceptData(points: self.points)
}
}
UPDATE
Since it seems that the current VC is presented by one of the tabBarController child controllers, you will have to access it through the self.presentingViewController:
#IBAction func submitbutton(_ sender: UIButton) {
pressed = true
if let customTabController = self.presentingViewController?.tabBarController as? CustomTabBarController {
customTabController.acceptData(points: self.points)
}
}
UPDATE 2
Your screenshot are of a very poor quality, your explanation of the problem would require a clarification too, since it is really hard to understand what you try to do. So after the whole discussion in comments I guess this is it:
#IBAction func submitbutton(_ sender: UIButton) {
pressed = true
if let tabController = self.presentingViewController?.tabBarController,
let viewController3 = tabController.viewControllers?.filter({ $0 is ViewController3 }).first {
viewController3.acceptData(points: self.points)
}
}
You can pass data as normally
let vc:HomeVC = ApiUtillity.sharedInstance.getCurrentLanguageStoryboard().instantiateViewController(withIdentifier: "HomeVC") as! HomeVC
vc.tempCategoryArray = CategoryArray
self.navigationController?.pushViewController(vc, animated: true)
In your TabBarController class, take a variable say variableToBeSet
class TabBarController: UITabBarController
{
var variableToBeSet: Int = 0 // This is just an example. You can change it as per requirement.
// Rest of class implementation
}
Now in your ViewController :
#IBAction func submitbutton(_ sender: UIButton) {
pressed = true
let tabControllerInstance = self.tabBarController as! TabBarController
tabControllerInstance.variableToBeSet = localVariable // The value which you want to assign
}

Send data only if view controller is being popped off stack by swipe or back button press but not by switching tabs

I looked through SO and compiled these methods below but none of them works for me.
I have a TabBarController with 2 tabs. In the Second tab I have a NavigationController > TableViewController > DetailViewController.
In my DetailViewController I have a custom delegate to send some data to the TableViewController when the Back Button is pressed or the view is Swiped to Dismiss (right swipe). I only want the data sent when the Back Button or Swipe to Dismiss is fully finished and not get sent when the tab is switched or if swiping 3/4 of the way but the user decides NOT to complete the back swipe (basically they stay on the same DetailVC scene).
I tried all of these methods below and they either get triggered when the tab is switched to the first tab, when the DetailVC gets pushed on and popped off, or during the 1/2 way Swipe to Dismiss the DetailVC they still run meaning the data should not have been sent.
DetailViewController:
protocol DetailViewDelegate: class {
func sendSomeData(value: Bool)
}
class DetailViewController: UIViewController{
weak var delegate: DetailViewDelegate?
//1. runs when Tab switches, the Back Button is pressed, and Swipe to Dismiss is triggered
override func viewWillDisappear(_ animated : Bool) {
super.viewWillDisappear(animated)
if (self.isMovingFromParentViewController) || (self.isBeingDismissed){
//doesn't run at all
}else{
//runs whenever view is no longer on scene
sendData()
}
}
//2. runs when Tab switches, Back Button is pressed, Swipe to Dismiss is triggered, and when the view is Pushed on AND Popped off
override func didMove(toParentViewController parent: UIViewController?) {
if parent != nil {
sendData()
}else{
//if parent == nil doesn't run at all
}
}
//3. if switching from the second tab it doesn't run but when switching back to the second tab it does run, also runs when view is being Pushed on and Not Popped on
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
sendData()
}
//4. if switching from the second tab it doesn't run but when switching back to the second tab it does run, also runs when view is being Pushed on and Not Popped on
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
sendData()
}
//MARK:- Custom Func
fileprivate func sendData(){
let value = true
delegate?.sendSomeData(value: value)
}
}
TableViewController:
class TableVC: UIViewController, DetailViewDelegate, UITableViewData..., UITableViewDele...{
var setValue = false
func sendSomeData(value: Bool){
//setValue should only update to true if DetailVC's Back Button is pressed or Right Swipe to Dismiss is fully complete
self.setValue = value
}
}
The TableView never has a problem receiving the data. The problem is when I switch tabs (data still gets sent) or a swipe to dismiss on the DetailVC isn't fully completed (data still gets sent).
What's the best way to send the data from the DetailVC but making sure the Back Button is pressed or Right Swipe to Dismiss is fully complete?
You need to use custom back button and a delegate to call the parent.
this is your parent ViewController:
import UIKit
class ViewController: UIViewController, ViewControllerSecondDelegate {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Next" {
let vc = segue.destination as? ViewControllerSecond
vc?.delegate = self
}
}
func secondDelegate() {
print("delegate") //GetData()
}
}
and this is the child view controller, which you want to back from it to your parent:
import UIKit
protocol ViewControllerSecondDelegate {
func secondDelegate()
}
class ViewControllerSecond: UIViewController, UIGestureRecognizerDelegate {
var isTouched = false
var isPopTouch = true
var delegate: ViewControllerSecondDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.setNavigationBarHidden(false, animated:false)
let myBackButton:UIButton = UIButton.init(type: .custom)
myBackButton.addTarget(self, action: #selector(ViewControllerSecond.popToRoot(sender:)), for: .touchUpInside)
myBackButton.setTitle("Back", for: .normal)
myBackButton.setTitleColor(.blue, for: .normal)
myBackButton.sizeToFit()
let myCustomBackButtonItem:UIBarButtonItem = UIBarButtonItem(customView: myBackButton)
self.navigationItem.leftBarButtonItem = myCustomBackButtonItem
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if isTouched {
isPopTouch = true
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.isPopTouch = false
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if isPopTouch {
delegate?.secondDelegate()
}
}
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer == self.navigationController?.interactivePopGestureRecognizer {
self.isTouched = true
}
return true
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.isTouched = false
}
func popToRoot(sender:UIBarButtonItem){
delegate?.secondDelegate()
self.navigationController?.popToRootViewController(animated: true)
}
}
the above code, handle back button and back gesture.

How to use callback to send back data to previous ViewController

I have 2 ViewControllers like this:
I want when I press button in ViewController will present to ViewController2. Then when I input the text in TextField and press Back Button in ViewController2, will send that text to button in ViewController1.
I can solve this with delegate or using reference the class ViewController in ViewController2. (You can see with my comment code)
But now I want to use CallBack to send back data.
How to do this:
Here is ViewController:
import UIKit
class ViewController: UIViewController {
#IBOutlet var button: UIButton!
var myString = ""
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
button.setTitle(myString, forState: .Normal)
}
#IBAction func goToViewController2(sender: AnyObject) {
let vc2 = storyboard?.instantiateViewControllerWithIdentifier("ViewController2") as! ViewController2
// vc2.vc1 = self
presentViewController(vc2, animated: true, completion: nil)
}
}
And ViewController2:
import UIKit
class ViewController2: UIViewController, UITextFieldDelegate {
#IBOutlet var textField: UITextField!
// var vc1: ViewController?
var callback: ((String) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
}
func textFieldDidEndEditing(textField: UITextField) {
callback!(textField.text!)
}
#IBAction func backToViewController(sender: AnyObject) {
// vc1?.myString = textField.text!
dismissViewControllerAnimated(true, completion: nil)
}
}
And I don't want to use with completion block in dismiss function, I just want to use my own callback method.
In vc1, set vc2's callback property before presenting it:
#IBAction func goToViewController2(sender: AnyObject) {
let vc2 = storyboard?.instantiateViewControllerWithIdentifier("ViewController2") as! ViewController2
vc2.callback = { <Put your callback code here...> }
presentViewController(vc2, animated: true, completion: nil)
}
And then, in vc2 call the callback function before dismissing the view controller:
#IBAction func backToViewController(sender: AnyObject) {
callback?(textfield.text!)
dismissViewControllerAnimated(true, completion: nil)
}