How can I properly reload the UIViewController after dismiss method is called? - swift

I have 2 viewControllers named vcA and vcB.
vcA has navigationController and vcB is just viewController which don't have navigationController.
I saved text data in a file in vcB, and I wanna show that text in vcA when I come back to vcA after the dismiss method is called.
I thought reloading vcA after dismiss is called is the proper way to show text, but I figured out that it might be difficult to do that between vcA and vcB, because vcA has navigationController but vcB doesn't have navigationController.
How can I refresh vcA?

You can use self.present over self.dismiss. Like this:
let vc = vcA()
And here you can pass data. For example, if vcA() has a var called text that is a string you can use vc.text = "something". And this data will go with the self.present method
vc.modalPresentationStyle = .fullScreen
self.present(vc,animated: true, completion: nil)

You can pass the value back to the presenting view controller using the protocol/delegate pattern. Following is an example of how to pass a text from a text field in vcB to vcA as vcB is being dismissed so you can substitute that with the value you want to pass.
protocol ValuePassingDelegate {
func didPassValue(of text: String)
}
class ViewControllerA: UIViewController, ValuePassingDelegate {
let label = UILabel(frame: CGRect(origin: CGPoint(x: 0, y: 100), size: CGSize(width: 200, height: 100)))
let button = UIButton(type: .system)
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(label)
label.text = "Initial text"
label.textAlignment = .center
button.frame = CGRect(origin: CGPoint(x: 0, y: 300), size: CGSize(width: 200, height: 100))
button.setTitle("Button", for: .normal)
button.addTarget(self, action: #selector(buttonHandler), for: .touchUpInside)
view.addSubview(button)
}
#objc func buttonHandler() {
let vcB = ViewControllerB()
vcB.delegate = self
self.navigationController?.pushViewController(vcB, animated: true)
}
func didPassValue(of text: String) {
DispatchQueue.main.async {
self.label.text = text
}
}
}
class ViewControllerB: UIViewController {
let textField = UITextField(frame: CGRect(origin: .zero, size: CGSize(width: 200, height: 100)))
var delegate: ValuePassingDelegate?
override func viewDidLoad() {
super.viewDidLoad()
textField.layer.borderWidth = 1
textField.layer.borderColor = UIColor.black.cgColor
view.addSubview(textField)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
delegate?.didPassValue(of: textField.text ?? "")
}
}

The way that I followed for this purpose is :
in vcB I added:
internal var senderVC: UIViewController?
in vcA I added:
vcB.senderVC = self
in vcB I added:
if let firstVC = self.senderVC as? vcA { firstVC.tableView.reloadData(); }

Related

TabBarController: always jump to root NavigationController

I have the following hierarchy in my app:
there is a TabBarController (X)
each of its items points to a NavigationController (A, B, C...)
each of those NavigationControllers starts a hierarchy (e.g. A1, A2, A3,...) of TableViewControllers
Now when the user is, say, in A3, they can press another TabBar item and jump to another hierarchy (say, the one of B). However, when they press the item for A, they will jump back to A3.
What I do want to happen, is for them to jump to A1 instead, which is essentially the "parent" UIView in the A hierarchy. Similarly, if they press B, they should jump to B1, not to wherever they were in the B hierarchy. I do not want to force the user to go back to e.g. A1 manually by hiding the bottom bar (the one from the TabBarController).
What is the best way to achieve this?
For visualisation:
/- A - A1 - A2 - A3
X ---- B - B1 - ...
\- C - ...
Programmatically, I currently have custom classes for X (TabBarController, TabBarControllerDelegate) and the TableViewControllers A1,...,B1,... . When the user presses an item, I can by debugging see that the target VC for the segue would be A/B/... and not A1/B1/... so I cannot control the process this way.
EDIT: check storyboard image below.
I'm not sure if I understood you correctly, but the bottomline is whenever the user taps on the tab bar you want the user to be able to see the root controller of each of the UINavigationController, correct?
You mentioned that you already have a custom TabBarController so have you tried the shouldSelect method?:
class CustomTabBarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
(viewController as? UINavigationController)?.popToRootViewController(animated: true)
return true
}
}
This seems to be working when I tried it on my playground.
Here's the full code I tried it with:
import PlaygroundSupport
import UIKit
class A: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.title = "A"
let label = UILabel(frame: CGRect(origin: .init(x: 100, y: 100), size: .init(width: 200, height: 100)))
label.text = "A"
self.view.addSubview(label)
let button = UIButton(frame: CGRect(origin: .init(x: 100, y: 200), size: .init(width: 200, height: 100)))
button.setTitle("Button", for: .normal)
button.addTarget(self, action: #selector(pressed), for: .touchUpInside)
button.backgroundColor = .black
self.view.addSubview(button)
}
#objc func pressed(_ sender: UIButton) {
let a1 = A1()
self.navigationController?.pushViewController(a1, animated: true)
}
}
class A1: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel(frame: CGRect(origin: .init(x: 100, y: 100), size: .init(width: 200, height: 100)))
label.text = "A1"
self.view.addSubview(label)
}
}
class B: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.title = "B"
let label = UILabel(frame: CGRect(origin: .init(x: 100, y: 100), size: .init(width: 200, height: 100)))
label.text = "B"
self.view.addSubview(label)
let button = UIButton(frame: CGRect(origin: .init(x: 100, y: 200), size: .init(width: 200, height: 100)))
button.setTitle("Button", for: .normal)
button.addTarget(self, action: #selector(pressed), for: .touchUpInside)
button.backgroundColor = .black
self.view.addSubview(button)
}
#objc func pressed(_ sender: UIButton) {
let b1 = B1()
self.navigationController?.pushViewController(b1, animated: true)
}
}
class B1: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel(frame: CGRect(origin: .init(x: 100, y: 100), size: .init(width: 200, height: 100)))
label.text = "B1"
self.view.addSubview(label)
}
}
class CustomTabBarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
(viewController as? UINavigationController)?.popToRootViewController(animated: true)
return true
}
}
let nav1 = UINavigationController(rootViewController: A())
let nav2 = UINavigationController(rootViewController: B())
let tabbarVC = CustomTabBarController()
tabbarVC.view.frame = CGRect(origin: .zero, size: CGSize(width: 500, height: 500))
tabbarVC.addChild(nav1)
tabbarVC.addChild(nav2)
PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = tabbarVC

child class inheriting from parent class not changing color

My swift code is trying to inherit view1 from parent class one to child class two. view1 is recongizined but when the code is run and the segue is applied nothing changes on the screen. view1 should change colors from pink to cyan. Its not I don't understand why the change is not being applied.
import UIKit
class one : UIViewController {
var view1 = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
[view1].forEach{
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
}
view1.frame = CGRect(x: 100, y: 100, width: 100, height: 100)
view1.backgroundColor = .systemPink
view.backgroundColor = .orange
view1.addTarget(self, action: #selector(move), for: .touchDown)
}
#objc func move(){
let vc = two()
vc.modalPresentationStyle = .overCurrentContext // actually .fullScreen would be better
self.present(vc, animated: true)
}
}
class two: one {
override func viewDidLoad() {
view1.backgroundColor = .cyan
}
}
Your code is working fine; the presentation of Two is happening. But you don't see anything, because:
The backgroundColor of Two's view is nil, i.e. .clear, so you don't see the background.
In Two, you never put anything into the interface. You talk to the button view1 in viewDidLoad, but unlike One's viewDidLoad, you never put that button into the interface. So you don't see the button (because it isn't there).
A minimal "fix" would be to call super in Two's viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad() // *
view1.backgroundColor = .cyan
}

swift playgrounds display uiview with nonstop looping

I create and display uiview in live view windows, when i create the button and add to the uiview , the program fail with nonstop looping which continuously load addbutton . Did somebody meet this problem and please tell me why :-)
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
var label1 : UILabel?
override func loadView() {
let view = UIView()
view.backgroundColor = .white
print("code run here ")
let label = UILabel()
label.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
label.text = "Hello World!"
label.textColor = .black
label1 = label
view.addSubview(label)
let k1:UIButton = addnewbutton() as! UIButton
//view.addSubview(k1)
self.view = view
}
#objc func buttonPressed(sender: UIButton!) {
var alertController = UIAlertController(title: "title", message: "message", preferredStyle: UIAlertControllerStyle.alert)
self.present(alertController, animated: true, completion: nil)
}
func addnewbutton() -> UIView{
var btn : UIButton
btn = UIButton()
btn.frame = CGRect(x:200,y:300,width:100,height:25)
btn.setTitle("clickme",for: UIControlState.normal)
//btn.titleLabel?.text = "clickme"
btn.backgroundColor = UIColor.black
btn.titleLabel?.textColor = UIColor.white
btn.titleColor(for: UIControlState.normal)
btn.addTarget(self, action: #selector(buttonPressed), for: UIControlEvents.touchUpInside)
view.addSubview(btn)
return btn
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
You add the button here
view.addSubview(btn)
inside addnewbutton
which recursively searches for the parent view of the VC and it's not yet setted inside loadView so control calls it again and the problem happens to infinite loop , so comment that line and uncomment this
view.addSubview(k1) // which is inside loadView
BTW make the return of addnewbutton to UIButton directly instead of a cast

Thread 1 Error on VC push?

My app currently has 3 VCs. The rootVC modally presents the second VC via UIButton--which works fine--but I'm having trouble getting the third VC appear after a UILabel tap in the second VC.
This is the code in the SecondVC that handles the tap:
var goToStats : UILabel {
var label = UILabel()
label.frame = CGRect(x:0, y:0, width: 300, height: 60)
label.center = CGPoint(x: view.center.x, y: view.center.y + 250)
label.text = "Statistical Breakdown"
label.font = UIFont(name: "Arial", size: 30)
label.textAlignment = .center
label.backgroundColor = UIColor(
displayP3Red: 1/255,
green: 102.0/255,
blue: 102.0/255,
alpha: 1)
label.isUserInteractionEnabled = true
label.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap)))
return label
}
view.addSubview(goToStats)
}
#objc func handleTap() {
print("Tapped!")
let thirdVC = ThirdVC()
self.navigationController!.present(thirdVC, animated: true, completion: nil)
}
Upon running, I get the following error:
error
Is there any explanation? I thought maybe there is no navigation Controller associated with the second VC (since it's returning nil), but the VC itself sits on a navigational stack, so I don't think that's the case here. Is it a problem with my third VC? Here is the current code:
import Foundation
import UIKit
class thirdVC : ViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.green
}
}
Thanks for your time!
Your rootViewController could be a navigationController but when you present something like this,
let secondVC = SecondVC()
self.navigationController!.present(secondVC, animated: true, completion: nil)
This doesn't mean that SecondVC will have a navigationController. To have a navigationController for SecondVC, you need to embed this ViewController inside a navigationController as below,
let secondVC = SecondVC()
let secondNavC = UINavigationController(rootViewController: secondVC)
self.navigationController!.present(secondNavC, animated: true, completion: nil)
Now, if you will present ThirdVC as below from SecondVC then it will work
let thirdVC = ThirdVC()
self.navigationController!.present(thirdVC, animated: true, completion: nil)
If you haven't embedded second ViewController inside a UINavigationController, then you will get that crash because you are force unwrapping(!) the navigationController which is not available in second ViewController.

Create segue objects programmatically

I created a single view app with 2 view controllers. In the first view controller, I have a label and 2 buttons that were programmatically created. I have 2 segues from these buttons to the second view controller. I wish that each button carries different information to the second view controller. From creating these buttons and segues programmatically, I haven’t been able to assign the identifier to the segues. So, I can’t use performSegue(withIdentifier: sender:).
How can I do this?
First ViewController:
import UIKit
class ViewController: UIViewController {
var myLabel: UILabel!
var leftButton: UIButton!
var rightButton: UIButton!
// bellow are the 2 variable which I’d like to transport into the second ViewController
var leftButtonText = "I'm the left button."
var rightButtonText = "I'm the right button."
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let homeNavBar = UINavigationBar()
homeNavBar.frame = CGRect(x: 0, y: 0, width: 320, height: 45)
homeNavBar.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(homeNavBar)
let homeNavItem = UINavigationItem(title: "Home")
homeNavBar.setItems([homeNavItem], animated: false)
myLabel = UILabel()
myLabel.frame = CGRect(x: 165, y: 150, width: 200, height: 42)
myLabel.text = "Press one of the button bellow!"
self.view.addSubview(myLabel)
leftButton = UIButton(type: .custom)
leftButton.frame = CGRect(x: 80, y: 300, width: 70, height: 70)
leftButton.backgroundColor = UIColor.cyan
leftButton.setTitleColor(UIColor.black, for: .normal)
leftButton.setTitle("Left Button", for: .normal)
leftButton.layer.cornerRadius = 20
leftButton.layer.borderWidth = 1
leftButton.layer.masksToBounds = true
leftButton.addTarget(self, action: #selector(ViewController.leftButtonAction(_:)), for: .touchUpInside)
self.view.addSubview(leftButton)
rightButton = UIButton(type: .custom)
rightButton.frame = CGRect(x: 240, y: 300, width: 70, height: 70)
rightButton.backgroundColor = UIColor.cyan
rightButton.setTitleColor(UIColor.black, for: .normal)
rightButton.setTitle("Right button", for: .normal)
rightButton.layer.cornerRadius = 20
rightButton.layer.borderWidth = 1
rightButton.layer.masksToBounds = true
rightButton.addTarget(self, action: #selector(ViewController.rightButtonAction(_:)), for: .touchUpInside)
self.view.addSubview(rightButton)
let margins = self.view.layoutMarginsGuide
homeNavBar.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
homeNavBar.topAnchor.constraint(equalTo: margins.topAnchor).isActive = true
homeNavBar.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
myLabel.topAnchor.constraint(equalTo: homeNavBar.bottomAnchor).isActive = true
leftButton.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
leftButton.topAnchor.constraint(equalTo: myLabel.bottomAnchor).isActive = true
rightButton.topAnchor.constraint(equalTo: myLabel.bottomAnchor).isActive = true
rightButton.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
}
#objc func leftButtonAction (_ sender: UIButton) {
let segueSecondViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "idSecond")
self.show(segueSecondViewController, sender: nil)
print("I'm activated by the left button.")
}
#objc func rightButtonAction (_ sender: UIButton) {
let segueSecondViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "idSecond")
self.show(segueSecondViewController, sender: nil)
print("I'm activated by the right button.")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Second ViewController:
import UIKit
class SecondViewController: UIViewController {
var mySecondLabel: UILabel!
var mySecondLabelText = "I'm the second page."
var closeButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let secondNavBar = UINavigationBar()
secondNavBar.frame.size = CGSize(width: 320, height: 45)
secondNavBar.translatesAutoresizingMaskIntoConstraints = false
let secondNavItem = UINavigationItem(title: "Second page")
secondNavBar.setItems([secondNavItem], animated: false)
self.view.addSubview(secondNavBar)
mySecondLabel = UILabel()
mySecondLabel.frame = CGRect(x: 165, y: 200, width: 200, height: 42)
mySecondLabel.backgroundColor = UIColor.yellow
mySecondLabel.textColor = UIColor.black
mySecondLabel.text = "\(mySecondLabelText)"
mySecondLabel.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(mySecondLabel)
closeButton = UIButton(type: .custom)
closeButton.frame = CGRect(x: 10, y: 50, width: 50, height: 50)
closeButton.backgroundColor = UIColor.red
closeButton.setTitleColor(UIColor.black, for: .normal)
closeButton.setTitle("Close", for: .normal)
closeButton.layer.cornerRadius = 30
closeButton.layer.borderWidth = 1
closeButton.layer.masksToBounds = true
closeButton.addTarget(self, action: #selector(SecondViewController.closeButtonAction(_:)), for: .touchUpInside)
self.view.addSubview(closeButton)
let margins = self.view.layoutMarginsGuide
secondNavBar.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
secondNavBar.topAnchor.constraint(equalTo: margins.topAnchor).isActive = true
secondNavBar.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
closeButton.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
closeButton.topAnchor.constraint(equalTo: secondNavBar.bottomAnchor).isActive = true
mySecondLabel.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
mySecondLabel.topAnchor.constraint(equalTo: closeButton.bottomAnchor).isActive = true
mySecondLabel.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true
}
#objc func closeButtonAction (_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
print("Second page closed.")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
You can do it by giving your View Controller an Identifier. This isn't a segue, but it will Instantiate your target VC:
let vc = UIStoryboard(name:"Main", bundle:nil).instantiateViewControllerWithIdentifier("identifier") as! SecondViewController
self.navigationController?.pushViewController(vc, animated:true)
You can set the VC Identifier on the Identity Inspector tab on the StoryBoard.
I just read in the documentation that you can't call the performSegue method if you don't have a segue with an identifier in the Storyboard, so WRT your requirement, you can't do it programmatically.
identifier The string that identifies the triggered segue. In
Interface Builder, you specify the segue’s identifier string in the
attributes inspector.