Thread 1 Error on VC push? - swift

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.

Related

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

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(); }

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

swift, embed ui view controller with navigation controller to allow for back button

I am trying to get a back button on my MyServiceTypeSelector() controller so that after i present MyServiceTypeSelector() I can go back to the BRPServiceSelector() controller , how can i do this ? do i some how need to embed it with a nav controller and if so i am not using storyboards so it would need to be done programmatically?
import Foundation
import UIKit
class BRPServiceSelector: UIViewController, UITextFieldDelegate {
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
let businessAccountLabel: UILabel = {
let label = UILabel()
label.text = "Business Account"
label.backgroundColor = .white
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
return label
}()
lazy var serviceSelectorButton: UIButton = {
let button = UIButton(type: .system)
button.backgroundColor = UIColor.black
button.setTitle("Select A Service Type?", for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.addTarget(self, action: #selector(presentServiceSelector), for: .touchUpInside)
button.layer.cornerRadius = 3
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
return button
}()
func presentServiceSelector(){
let msts = MyServiceTypeSelector()
let navController = UINavigationController(rootViewController: msts)
self.present(navController, animated: true, completion: nil)
let containerView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .white
return v
}()
let scrollView: UIScrollView = {
let v = UIScrollView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .white
return v
}()
func setupViews(){
containerView.addSubview(serviceSelectorButton)
serviceSelectorButton.anchor(top: containerView.topAnchor, left: nil, bottom: nil, right: nil, paddingTop: 50, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 220, height: 25)
serviceSelectorButton.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
}
}
}
If you want the VC in the navigation stack then push it onto the stack instead of presenting it. presenting is normally used for modal windows and they dont usually have navigation bars.
self.navigationController?.pushViewController(vc, animated: true)

Swift, Add a navigation controller in custom activity indicator

My Custom Activity indicator is not overlapping Navigation controller.
Below is my code
func showActivityIndicator(uiView: UIView) {
container.frame = uiView.frame
container.center = uiView.center
container.backgroundColor = UIColorFromHex(0xffffff, alpha: 0.1)
var loadingView: UIView = UIView()
loadingView.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height)
loadingView.center = uiView.center
loadingView.backgroundColor = UIColorFromHex(0x444444, alpha: 0.5)
loadingView.clipsToBounds = true
loadingView.layer.cornerRadius = 10
var imageData = NSData(contentsOfURL: NSBundle.mainBundle()
.URLForResource("synch-loader", withExtension: "gif")!)
let try = UIImage.animatedImageWithData(imageData!)
var imageView = UIImageView(image: try)
imageView.center = uiView.center
imageView.frame = CGRect(x: uiView.frame.width/4, y: uiView.frame.height/2, width: 500, height: 15)
loadingView.addSubview(imageView)
container.addSubview(loadingView)
uiView.addSubview(container)
actInd.startAnimating()
}
func navigationHandling()
{
if self.navigationController != nil {
self.navigationController?.navigationBar.tintColor = utility.uicolorFromHex(0x70B420)
self.navigationController?.navigationBar.barTintColor = utility.uicolorFromHex(0x70B420)
self.navigationController?.navigationBarHidden = false
self.navigationItem.hidesBackButton = true
}
sortingBtn = UIBarButtonItem(image: sortingImg, style: UIBarButtonItemStyle.Done, target: self, action: Selector("sortingPressed:"))
menuBtn = UIBarButtonItem(image: menuImg, style: UIBarButtonItemStyle.Plain, target: self, action : nil)
sortingBtn.tintColor = UIColor.whiteColor()
menuBtn.tintColor = UIColor.whiteColor()
var buttons : NSArray = [menuBtn,sortingBtn]
self.navigationItem.rightBarButtonItems = buttons as [AnyObject]
self.navigationItem.setRightBarButtonItems([menuBtn,sortingBtn], animated: true)
networkLabel.hidden = true
}
I just want to overlap the view on navigation controller so that it don't looks ugly.
I appreciate help!
let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate
let window = appDelegate?.window
window.addSubview("yourActivityIndicator")
Here add the activity indicator to the window, or pass the window in as the view.
let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate
let window = appDelegate?.window
showActivityIndicator(window)
The uiView you're passing as parameter to this method
func showActivityIndicator(uiView: UIView)
is your ViewController's view or your UINavigationController's view?
Because if it's your UIViewController's view your custom loading will take it's size and origin, that is under your NavigationBar