问题
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.
回答1:
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.
回答2:
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)
- ...
来源:https://stackoverflow.com/questions/43644972/updating-uilabel-text-when-variable-changes