1

I've got a UIControl class and need to do some calculation based on UIImageView location which can be moved with touchesBegan and touchesMoved (everything inside this class). Than I would like to display it as a UILabel which I've created programmatically.

class control : UIControl{
   ...
   let leftControl: UIImageView = UIImageView(image: UIImage(named: "left-control"))
   ...
   func leftValue() -> String{
       var leftValue : String = "0.0"
       leftValue = "\(leftControl.center.x)"
       return leftValue
   }
}

and my ViewController.swift

class ViewController: UIViewController {

let ctrl : Control = Control()
let leftLabel : UILabel = UILabel(frame: CGRect(x: 40, y: 300, width: 150, height: 30))

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    ctrl.frame.origin = CGPoint(x: 40, y: 400)

    leftLabel.text = "\(ctrl.leftValue())" //displays only starting value

    view.addSubview(slider)
    view.addSubview(leftLabel)
    view.addSubview(rightLabel)
}

I know that it's inside the viewDidLoad so it's not updating properly. I was wondering about scheduledTimer but don't know if it's good solution.

mikro098
  • 2,173
  • 2
  • 32
  • 48

2 Answers2

2

You can achieve this using protocols and delegation - in the file for your Control add this :

protocol ControlDelegate: class {
    func controlPositionDidChange(leftValue: String)
}

And add a weak var delegate: ControlDelegate? inside Control class.

In the file for view controller make following changes :

class ViewController: UIViewController, ControllDelegate {

let ctrl : Control = Control() 
let leftLabel : UILabel = UILabel(frame: CGRect(x: 40, y: 300, width: 150, height: 30))

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    ctrl.frame.origin = CGPoint(x: 40, y: 400)
    ctrl.delegate = self

    leftLabel.text = "\(ctrl.leftValue())" //displays only starting value

    view.addSubview(slider)
    view.addSubview(leftLabel)
    view.addSubview(rightLabel) 
}

func controlPositionDidChange(leftValue: String) {
    leftLabel.text = leftValue
}
}

Now, whenever you want to inform the delegate that your control has changed the position, simply call self.delegate?.controlPositionDidChange(self.leftValue()) in appropriate places.

As usually, there is more in the docs. I highly suggest reading through them as delegation and protocols are widely used in CocoaTouch.

Losiowaty
  • 7,911
  • 2
  • 32
  • 47
  • dzięki za pomoc ;) – mikro098 Apr 27 '17 at 09:01
  • Nie ma za co :) But let's keep things in English here ;) – Losiowaty Apr 27 '17 at 09:03
  • one more question - why did you write `protocol ControlDelegate: class{ }`. Does it mean that this ControlDelegate inherits from the class? What was the purpose? – mikro098 Apr 27 '17 at 23:50
  • 1
    This is due to the fact that only `class` protocols can be `weak` - you can [read more here](http://stackoverflow.com/questions/33471858/swift-protocol-weak-cannot-be-applied-to-non-class-type) and as usual [in the docs.](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID281) – Losiowaty Apr 28 '17 at 04:06
0

The answer of @Losiowaty describes the solution that most developers choose. But there are (in my opinion) much better ways to achieve it. I prefer the object oriented solution. It might look like more code, but its a much better maintainable way with more reusable code.

A real object oriented solution of your problem might look like that:

// reusable protocol set

protocol OOString: class {
    var value: String { get set }
}

// reusable functional objects

final class RuntimeString: OOString {

    init(initialValue: String = "") {
        self.value = initialValue
    }

    var value: String

}

final class ViewUpdatingString: OOString {

    init(_ decorated: OOString, view: UIView) {
        self.decorated = decorated
        self.view = view
    }

    var value: String {
        get {
            return decorated.value
        }
        set(newValue) {
            decorated.value = newValue
            view.setNeedsLayout()
        }
    }

    private let decorated: OOString
    private let view: UIView
}

// reusable ui objects

final class MyLabel : UILabel {

    init(frame: CGRect, imageXCenter: OOString) {
        self.imageXCenter = imageXCenter
        super.init(frame: frame)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("Not supported")
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        text = imageXCenter.value
    }

    private let imageXCenter: OOString

}

final class MyControl : UIControl {

    init(frame: CGRect, imageXCenter: OOString) {
        self.imageXCenter = imageXCenter
        super.init(frame: frame)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("Not supported")
    }

    private let imageXCenter: OOString
    private let leftControl = UIImageView(image: UIImage(named: "left-control"))

    // call this at change
    private func updateValue() {
        imageXCenter.value = "\(leftControl.center.x)"
    }

}

// non reusable business logic

final class MyViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let dependency = RuntimeString(initialValue: "unset")
        let leftLabel = MyLabel(
            frame: CGRect(x: 40, y: 300, width: 150, height: 30),
            imageXCenter: dependency
        )
        let control = MyControl(
            frame: CGRect(x: 40, y: 400, width: 400, height: 400),
            imageXCenter: ViewUpdatingString(dependency, view: leftLabel)
        )
        view.addSubview(leftLabel)
        view.addSubview(control)
    }

}

The main idea is to extract the dependency of both objects into another object and use a decorator then to automatically update the ui on every set value.

Note:

  • this approach follows the rules of object oriented coding (clean coding, elegant objects, decorator pattern, ...)
  • reusable classes are very simple constructed and fullfill exactly one task
  • classes communicate by protocols with each other
  • dependencies are given by dependency injection as far as possible
  • internal object functionality is private (loose coupling)
  • everything (except the business logic) is designed for reuse -> if you code like that the portfolio of reusable code grows with every day you code
  • the business logic of your app is more concentrated in one place (in my real coding its even outside the UIViewController)
  • unittesting of reusable objects is very simple when using fake implementations for the protocols (even mocking is not needed in most cases)
  • lesser problems with retain cycles, in most cases you do not need weak properties any more
  • avoiding Null, nil and Optionals (they pollute your code)
  • ...
ObjectAlchemist
  • 1,109
  • 1
  • 9
  • 18