"Use of Unresolved Identifier" Error when Variable Declared in loadView() - ios

I am trying to create a simple app where when a user taps, the label text will change, as shown in the code below. However, in my function that handles the tap, it says that label is unresolved. I believe this is because the label is loaded in the loadView and cannot be accessed throughout the class (I am a beginner Swift user, so forgive me if I'm wrong.) Here is my code:
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let containerView = UIView (frame: CGRect(x: 0, y: 0, width: 600, height: 600))
containerView.backgroundColor = UIColor.white
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
label.center = CGPoint(x: 160, y: 285)
label.textAlignment = .center
label.text = "Hey"
containerView.addSubview(label)
self.view = containerView
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
view.addGestureRecognizer(tap)
}
#objc func handleTap(sender:UITapGestureRecognizer) {
label.text = "Changed"
}
}
PlaygroundPage.current.liveView = MyViewController()
Thank you!

You have declared label as a local variable inside loadView - This means it is only accessible in the loadView function. You need it to be a property so that it is accessible throughout your class.
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
var label: UILabel!
override func loadView() {
let containerView = UIView (frame: CGRect(x: 0, y: 0, width: 600, height: 600))
containerView.backgroundColor = UIColor.white
self.label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
self.label.center = CGPoint(x: 160, y: 285)
self.label.textAlignment = .center
self.label.text = "Hey"
containerView.addSubview(self.label)
self.view = containerView
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
view.addGestureRecognizer(tap)
}
#objc func handleTap(sender:UITapGestureRecognizer) {
self.label.text = "Changed"
}
}
PlaygroundPage.current.liveView = MyViewController()

You are trying to access label which is declared within loadView() method. handleTap() does not have visibility/access to label.
Declare label as class variable.
Refer here for more information about declaration and scope: https://docs.swift.org/swift-book/ReferenceManual/Declarations.html

Related

How to set the frame of a ViewController without storyboard

I want to create a view (e.g. 100x100) with ViewController without using the storyboard. I am wondering what the best way to declare the frame of the ViewController is.
I tried:
class MyLittleViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
}
}
And I tried to see this view on my MainViewController:
override func viewDidLoad() {
super.viewDidLoad()
let myLittleView = MyLittleViewController()
myLittleView.willMove(toParent: self)
myLittleView.view.translatesAutoresizingMaskIntoConstraints = false
myLittleView.view.backgroundColor = .red
view.addSubview(myLittleView.view)
// enable auto-sizing (for example, if the device is rotated)
myLittleView.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.addChild(myLittleView)
myLittleView.didMove(toParent: self)
myLittleView.view.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
myLittleView.view.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
It doesn't work like I expected because the little view doesn't appear on the main view. Any hints?
You shouldn't mix frame layout with auto-layout set a width & height constraints also
NSLayoutConstraint.activate([
myLittleView.view.centerXAnchor.constraint(equalTo: view.centerXAnchor),
myLittleView.view.centerYAnchor.constraint(equalTo: view.centerYAnchor),
myLittleView.view.heightAnchor.constraint(equalToConstant:100),
myLittleView.view.widthAnchor.constraint(equalToConstant:100)
])
OR
myLittleView.view.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
myLittleView.view.center = view.center
OR
override func loadView() {
view = UIView()
view.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
}
Edit:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .green
let myLittleView = MyLittleViewController()
myLittleView.willMove(toParent: self)
myLittleView.view.backgroundColor = .red
view.addSubview(myLittleView.view)
// enable auto-sizing (for example, if the device is rotated)
myLittleView.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.addChild(myLittleView)
myLittleView.didMove(toParent: self)
myLittleView.view.center = view.center
}
}
class MyLittleViewController: UIViewController {
override func loadView() {
view = UIView()
view.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
}
}
Another way to set frame of the view controller is override method loadView() and set the view frame like this
func loadView() {
view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
}

Why is my button not working in Swift Playgrounds?

I am trying to make a playground and I have a button that says "Let's play!" and moves into a new view controller.
I looked at the code from this website and put it into my code:
http://lab.dejaworks.com/ios-swift-3-playground-uibutton-action/
This is all of my code (like, all of it):
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
//Introduction
let view = UIView()
view.backgroundColor = .red
//title
func labelCool() {
let label = UILabel()
label.frame = CGRect(x: 50, y: 300, width: 400, height: 100)
label.text = "Add-Add - A Wonderful Game!"
//label.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
label.numberOfLines = 3
label.font = UIFont(name: "HelveticaNeue-Bold", size: 30)
UILabel.animate (withDuration: 10.0, animations:{
label.textColor = .black
})
UILabel.animate(withDuration: 5.0, animations:{
label.textColor = .blue
})
view.addSubview(label)
}
labelCool()
//subtitle
let subtitle = UILabel()
subtitle.frame = CGRect(x: 50, y: 400, width: 200, height: 50)
subtitle.text = "Curated and Created by Yours Truly, Adit Dayal!"
subtitle.numberOfLines = 4
self.view = view
view.addSubview(subtitle)
}
override func viewDidLoad() {
class Responder : NSObject {
#objc func action() {
print("Yay!")
}
}
let responder = Responder()
//next page
let button = UIButton(frame : CGRect(x: 0, y: 500, width: 200, height: 50))
button.setTitle("Let's Play!", for: .normal)
button.backgroundColor = .blue
button.addTarget(responder, action: #selector(Responder.action), for: .touchUpInside)
view.addSubview(button)
}
}
class gameViewController: UIViewController {
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
For now, I just want the button to display "Yay!" when clicked, but it is doing nothing!
Does anyone know why? (I'm on a bit of a time constraint)
Thank you so much,
Adit Dayal
Your Responder class is inside the viewDidLoad() function put the class outside like so
class Responder : NSObject {
#objc func action() {
print("Yay!")
}
}
override func viewDidLoad() {
let responder = Responder()
//next page
let button = UIButton(frame : CGRect(x: 0, y: 500, width: 200, height: 50))
button.setTitle("Let's Play!", for: .normal)
button.backgroundColor = .blue
button.addTarget(responder, action: #selector(responder.action), for: .touchUpInside)
view.addSubview(button)
}
The problem is that you are creating the responder object inside the viewDidLoad, as a local variable; this cause the object to be destroyed when the function ends (but we want that object alive even after). You have to retain that object, so instead of creating a local variable, create an instance variable by simply saving it as a class scope:
class Responder : NSObject {
#objc func action() {
print("Yay!")
}
}
let responder = Responder() // this is now outside the viewDidLoad, so it's an instance variable
override func viewDidLoad() {
//next page
let button = UIButton(frame : CGRect(x: 0, y: 500, width: 200, height: 50))
button.setTitle("Let's Play!", for: .normal)
button.backgroundColor = .blue
button.addTarget(responder, action: #selector(Responder.action), for: .touchUpInside)
view.addSubview(button)
}

Swift Playground in Xcode - TapGestureRecognizer to UILabel not working

I am creating a Swift Playground in Xcode and I have a problem with putting a TapGestureRecognizer to an UILabel...
class MyViewController : UIViewController {
override func loadView() {
func tapped() {
print("The label was tapped!")
}
let label11 = UILabel()
label11.frame = CGRect(x: -350, y: 360, width: 350, height: 20)
label11.text = "name."
label11.textColor = .black
label11.isUserInteractionEnabled = true
view.addSubview(label11)
let tap = UITapGestureRecognizer(target: self, action: "tapped")
label11.addGestureRecognizer(tap)
}
}
It appears that you're creating the view controller programmatically since you're operating solely in loadView (where you correctly did not call super), which requires you to create the actual view of that controller.
import UIKit
import PlaygroundSupport
class MyViewController: UIViewController {
override func loadView() {
view = UIView()
let label11 = UILabel()
label11.frame = CGRect(x: 0, y: 360, width: 350, height: 20)
label11.text = "name."
label11.textColor = .yellow
label11.isUserInteractionEnabled = true
view.addSubview(label11)
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
label11.addGestureRecognizer(tap)
}
#objc func tapped() {
print("The label was tapped!")
}
}
PlaygroundPage.current.liveView = MyViewController()
Alternatively, you can replicate a non-programmatic view controller by using viewDidLoad (which requires you to call super).
import UIKit
import PlaygroundSupport
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let label11 = UILabel()
label11.frame = CGRect(x: 0, y: 360, width: 350, height: 20)
label11.text = "name."
label11.textColor = .yellow
label11.isUserInteractionEnabled = true
view.addSubview(label11)
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
label11.addGestureRecognizer(tap)
}
#objc func tapped() {
print("The label was tapped!")
}
}
PlaygroundPage.current.liveView = MyViewController()
Also, your origin-x value was out of bounds which is why you may not have seen the label and you were missing #objc syntax which is required in Swift 4.

Trying to understand why this crashes when switch changes

learning Swift 3 - not sure why the code to change the background color of my button crashes the app. myButton is a class variable and should be available to the function correct?
class ViewController: UIViewController {
var myButton : UIButton!
var mySwitch : UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
let firstView = UIView()
firstView.backgroundColor = UIColor.white
view = firstView
let myNewView = UIView( frame: CGRect(x:65, y:100, width:300, height:200))
myNewView.backgroundColor = UIColor.blue
//myNewView.layer.cornerRadius = 25
//myNewView.layer.borderWidth = 2
//myNewView.layer.borderColor = UIColor.red.cgColor
self.view.addSubview(myNewView)
let switchDemo=UISwitch();
switchDemo.isOn = true
switchDemo.setOn(true, animated: false);
switchDemo.addTarget(self, action: #selector(ViewController.switchValueDidChange(sender:)), for: .valueChanged);
let myButton = UIButton(frame: CGRect(x: 0, y: 0, width: 144, height: 144))
myButton.backgroundColor = UIColor.red
myButton.addTarget(self, action: #selector(ViewController.ratingButtonTapped(sender:)), for: .touchUpInside)
self.view.addSubview(myButton)
myButton.addSubview(switchDemo);
}
func switchValueDidChange( sender:UISwitch){
print("Switch changed")
myButton.backgroundColor = UIColor.green
}
func ratingButtonTapped(sender:UIButton!)
{
print("Button It Works!!!")
}
}
First of all and strictly spoken myButton is an instance variable (actually a property) rather than a class variable.
let myButton = creates a local variable with the same name as the property but it is not the same object.
The solution is to omit let
myButton = UIButton(frame: CGRect(x: 0, y: 0, width: 144, height: 144))
It's simple. You declare a class property but you don't instantiate it. I your viewDidLoad you just instantiate a local variable myButton, instead of instantiating the class property.
Here is a fix:
myButton = UIButton(frame: CGRect(x: 0, y: 0, width: 144, height: 144))
Just remove the let keyword.
You did not instantiate myButton anywhere in your code, therefore it stays nil.
You are, however, declaring a local variable called myButton in viewDidLoad.
You should replace your instantiation with this:
myButton = UIButton(frame: CGRect(x: 0, y: 0, width: 144, height: 144))

UINavigationItem TitleView disappears

I am trying to create a custom titleView for a navigation bar. I am able to set the titleView in the root view controller that is embedded in a navigation controller.
When I push the second view controller onto the stack and try to set the titleView for this view controller it does not work. The titleView quickly appears and disappears. When I go back to the previous view controller this titleView quickly appears and disappears now also.
Does anyone know why this is happening or how to set the titleView correctly without flashing and disappearing?
class FirstViewController: UIViewController {
var titleView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
addTitleView()
}
func addTitleView() {
titleView = UIView(frame: CGRect(x: 0, y: 0, width: 150, height: 44))
let companyLabel = UILabel(frame: CGRect(x: 0, y: 3, width: 150, height: 11))
companyLabel.text = "CPS Dashboard"
companyLabel.textColor = UIColor.grayColor()
companyLabel.textAlignment = .Center
companyLabel.font = UIFont.systemFontOfSize(9)
titleView.addSubview(companyLabel)
let titleLabel = UILabel(frame: CGRect(x: 0, y: 16, width: 150, height: 18))
titleLabel.text = "Dashboard"
titleLabel.textColor = UIColor.blackColor()
titleLabel.textAlignment = .Center
titleLabel.font = UIFont.systemFontOfSize(15)
titleView.addSubview(titleLabel)
navigationItem.titleView = titleView
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Show" {
let controller = segue.destinationViewController as! SecondViewController
controller.titleView = titleView
}
}
}
The second viewcontroller:
class SecondViewController: UIViewController {
var titleView: UIView?
override func viewDidLoad() {
super.viewDidLoad()
if let titleView = titleView {
navigationItem.titleView = titleView
}
}
}
I found a solution. I copied addTitleView() method from FirstViewController into SecondViewController, and called both of them in viewDidLoad(). This worked exactly as I wanted it to. For some reason it was not working to pass the titleView forward as a property and assigning it to navigationItem.titleView.
class FirstViewController: UIViewController {
var titleView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
addTitleView()
}
func addTitleView() {
titleView = UIView(frame: CGRect(x: 0, y: 0, width: 150, height: 44))
let companyLabel = UILabel(frame: CGRect(x: 0, y: 3, width: 150, height: 11))
companyLabel.text = "CPS Dashboard"
companyLabel.textColor = UIColor.grayColor()
companyLabel.textAlignment = .Center
companyLabel.font = UIFont.systemFontOfSize(9)
titleView.addSubview(companyLabel)
let titleLabel = UILabel(frame: CGRect(x: 0, y: 16, width: 150, height: 18))
titleLabel.text = "Dashboard"
titleLabel.textColor = UIColor.blackColor()
titleLabel.textAlignment = .Center
titleLabel.font = UIFont.systemFontOfSize(15)
titleView.addSubview(titleLabel)
navigationItem.titleView = titleView
}
}
The second viewcontroller:
class SecondViewController: UIViewController {
var titleView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
addTitleView()
}
func addTitleView() {
titleView = UIView(frame: CGRect(x: 0, y: 0, width: 150, height: 44))
let companyLabel = UILabel(frame: CGRect(x: 0, y: 3, width: 150, height: 11))
companyLabel.text = "CPS Dashboard"
companyLabel.textColor = UIColor.grayColor()
companyLabel.textAlignment = .Center
companyLabel.font = UIFont.systemFontOfSize(9)
titleView.addSubview(companyLabel)
let titleLabel = UILabel(frame: CGRect(x: 0, y: 16, width: 150, height: 18))
titleLabel.text = "Dashboard"
titleLabel.textColor = UIColor.blackColor()
titleLabel.textAlignment = .Center
titleLabel.font = UIFont.systemFontOfSize(15)
titleView.addSubview(titleLabel)
navigationItem.titleView = titleView
}
}
My solution is simple, and it works:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let tv = navigationItem.titleView {
print("transform", tv.transform)) // is always identity
let bounds = tv.bounds
print("bounds", bounds) // its origin may not be zero.
tv.bounds = CGRect(origin: .zero, size: bounds.size)
print("new bounds", tv.bounds)
}
}
Using Xcode's view debugger, you will find that titleView.bounds.origin is not zero.
How to let it happen again, two steps:
1. UIViewController A and B; A has custom navigationItem.titleView, B hides navigationBar in its viewWillAppear(); when B poped, A.viewWillAppear() setNavigationBar(hidden: false, animated: true)
2. user-driven popViewController is canceled by lifting your hand.
Then you will found, A's navigationBar is blank.
I was having this same issue, but none of the above solutions fixed it for me. My issue was that I was setting translatesAutoresizingMaskIntoConstraints to false. I imagine this caused the appearing/disappearing because it needs to be set to true in order to constrain the view internally to the navigation bar.

Resources