Search code examples
iosswiftuicontrol

How to access the value of a UIControl from another UIView class


Beginner question. I have a UISlider on the storyboard, and in another UIView class besides the ViewController I would like to use the slider's value to change path/shape variables within the makeShape() function.

I have tried:

a) In OtherView, try to directly reference ViewController's global variable val. This results in "unresolved identifier"

b) In ViewController, try to assign mySlider.value to a variable declared in OtherView, otherVal. This results in "Instance member 'otherVal' cannot be used on type 'OtherView'".

Could someone please help me learn how to do this correctly?

ViewController.swift:

class ViewController: UIViewController {

    var val: CGFloat = 0.0

    @IBOutlet weak var mySlider: UISlider!

    @IBOutlet weak var otherView: UIView!

    @IBAction func mySliderChanged(_ sender: Any) {
        val = CGFloat(mySlider.value)
        setOtherVal()
    }

    func setOtherVal() {
        otherView.otherVal = val
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        let otherView = OtherView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 100, height: 100)))

        self.view.addSubview(otherView)
    }
}

OtherView.swift:

class OtherView: UIView {

    var path: UIBezierPath!

    var otherVal: CGFloat = 0.0

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = UIColor.clear
        makeShape()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    func makeShape() {
        let width: CGFloat = self.frame.size.width
        let height: CGFloat = self.frame.size.height

        let path1 = UIBezierPath(ovalIn: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 50, height: 50)))

        let shapeLayer1 = CAShapeLayer()
        shapeLayer1.path = path1.cgPath
        shapeLayer1.fillColor = UIColor.blue.cgColor

        self.layer.addSublayer(shapeLayer1)

        shapeLayer1.position = CGPoint(x: otherVal, y: height)
    }
}

Solution

  • Right now you're trying to access the otherVal property in ViewController on the class OtherVal which is shared globally through the whole app, not the property on the instance of OtherVal you've created in ViewController.

    The simplest fix here is to store the OtherView instance you created in viewDidAppear and use that property to update the view.

    class ViewController: UIViewController {
    
        var val: Float = 0.0
        @IBOutlet weak var mySlider: UISlider!
    
        // Add a property to hold on to the view after you create it. 
        var otherView: OtherView?
    
        @IBAction func mySliderChanged(_ sender: Any) {
            val = mySlider.value
            setOtherVal()
        }
    
        func setOtherVal() {
            // `val` is a Float and `otherVal` is a `CGFloat` so we have to convert first.
            let newValue = CGFloat(self.val)
            // Use the property that's holding your view to update with the new value from the slider.
            self.otherView?.otherVal = newValue
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
        }
    
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
    
            let otherView = OtherView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 100, height: 100)))
    
            // Set the property to the view you just created so you can update it later.
            self.otherView = otherView
    
            self.view.addSubview(otherView)
        }
    }
    
    

    Currently though it doesn't look like you're actually updating OtherView once the new value is set, so you'll want to do something similar there. Keep a reference to the CAShapeLayer you create and update the position when otherVal changes.

    Also note viewDidAppear can be called multiple times throughout a view controller's life cycle. If you only want to add the view once, you should use viewDidLoad which will guarantee you have a freshly created self.view to work with.

    Edit: As mentioned in the comments, you can also set up a custom view in the storyboard / nib and create an outlet. Right now it doesn't look like your code is set up to work with that though.