Extend UIView with new properties - ios

In my app I need to add to a bunch of UIKit objects some properties; my original thought was to create a subclass of each element I needed and create the properties inside the new class but I realized that this means to write a new class for each UI element type I'm using.
In my specific case, I wanna add to some different views, such as UIImageView and UILabel, two properties called initial position and final position of type CGRect to store the initial and final position in order to use it inside a method which translates this views.
Is there any way to accomplish this without creating lots of classes?

You can do the following:
protocol Position {
var initialPosition: Int { get set }
}
It is not possible to just declare properties in extensions, so you'd need to set your get and set. You can just associate a value:
private var initialPositionKey: UInt = 0
extension Position {
var initialPosition: Int {
get {
return objc_getAssociatedObject(self, &initialPositionKey) as! Int
}
set {
objc_setAssociatedObject(self, &initialPositionKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
}
Then you extend your UIView like this:
extension UIView: Position {}
And the following works:
var view = UIView()
view.initialPosition = 5
print (view.initialPosition) // 5

Related

Append filtered string array to UILables - Swift

Long title! I do apologize.
Expected Outcome: Display uniqued string value in UILabel inside a UIView in a stackView. There may be multiple UIViews in the stackView. The stackView lives inside a tableCell. wow....
The views and cells are custom and I am not allowed to create sections. I have to work with what's in the existing codebase.
Issue I am stuck at trying to get the unique optional string values into the respective UILabels. I have a working extension to get unique items from an array. But I just don't know where to implement it, to get the unique values I need.
Code:
// Sample Model Structs
struct Parent {
var child: [Child]?
}
struct Child {
var childValue: String?
}
class TableViewCell {
var stackView = UIStackView()
func configureCellFrom(parent: Parent) {
/// Other code lives in the func to use the Parent struct.
if let child = parent.child {
if child.count > 1 {
tableCell.topLabel.text = "Multiple Child Values"
tableCell.ShowChildViewsButton.isHidden = false
for value in child {
let view = CustomUIView()
view.childValue.text = value.childValue.uniqued()
self.stackView.addArrangedSubview(view)
}
}
}
}
}
extension Sequence where Element: Hashable {
func uniqued() -> [Element] {
var set = Set<Element>()
return filter { set.insert($0).inserted }
}
}
Above Problem: Where I placed the uniqued() method, will parse out the individual characters in the string. So I know that it is one level too deep. What's the best way to achieve my required result?
The issue there is that uniqued method uses filter method declared on Sequence which will always return an array of the Sequence.Element in the case of a String an array of characters [Character]. You can simply initialize a new string with the array or characters or improve that method to support strings as well. To make that method support strings you need to extend RangeReplaceableCollection. There is a filter method declared on RangeReplaceableCollection which returns Self therefore if you filter a string it will return another string as I showed in this answer from the same post where you found the uniqued method you've shown in your question:
extension RangeReplaceableCollection where Element: Hashable {
var orderedSet: Self {
var set = Set<Element>()
return filter { set.insert($0).inserted }
}
}
Usage:
view.childValue.text = value.childValue?.orderedSet
Try this:
for (i, value) in child.enumerated() {
let view = CustomUIView()
view.childValue.text = value.childValue.uniqued()[i]
self.stackView.addArrangedSubview(view)
}

Elegant way to pass a "partial closure"?

In my ViewController, I am setting up multiple so-called PanelControls.
These PanelControls are initialized with a title, UISlider and information, what property of another UIView called ViewToEdit to change with that slider (ControlPanel has a reference to it).
It is always one single property of type CGFloat or UIColor.
I want to be able to pass a property (not a value) of ViewToEdit when initializing a PanelControl.
So I wanna use it like this:
PanelControl(title: "doesnt matter", propertyToEdit: ViewToEdit.propertyToEdit)
And PanelControl would implement it like this:
class PanelControl: UIView {
...
func sliderChanged(slider: UISlider) {
propertyToEdit = slider.value
}
}
please not that the above code is just my fantasy and doesn't actually work. It just illustrates my desired usage.
This way I could create many instances of PanelControl and pass each one different information on which property of ViewToEdit they control.
I have tried:
Using a closure, but that does not fit because it is not a complete statement I want to pass. Rather a part of a statement. So viewToEdit.propertyToEdit = ... with the right side of that statement set by PanelControl when executing it.
Literally passing it ViewToEdit.propertyToEdit but that obviously makes no sense as well.
What to I do?
How about using key paths:
class PanelControl: UIView {
let changer: (Float) -> Void
init<T: AnyObject>(title: String, object: T, property: ReferenceWritableKeyPath<T, Float>) {
changer = { object[keyPath: property] = $0 }
}
func sliderChanged(slider: UISlider) {
changer(slider.value)
}
}
You can use it like this:
PanelControl(title: "doesnt matter", object: myViewModel, property: \ViewToEdit.propertyToEdit)

How to create a new field in UITextField extension?

I am new to Swift and maybe it's a stupid question, but I can't find an answer to it.
I have created an extension:
extension UITextField {
var placeholderLabel: UILabel {
get {
return self.placeholderLabel
}
set {
self.placeholderLabel = newValue
}
}
}
When the property is set, the application crashes.
You can't have a stored property in extension.
Extensions are not allowed to add a property to existing class because adding a property structure of the class will change. And because Objective C, Swift or any other programming language that am aware of could not afford it, it won't allow you to add the stored property to extension.
Isn't there any work around then ??
This is what you can do to save the label as stored property in your extension :)
import Foundation
import UIKit
fileprivate var ascociatedObjectPointer : UInt8 = 99
extension UITextField {
var myLabel : UILabel {
get {
return objc_getAssociatedObject(self, &ascociatedObjectPointer) as! UILabel
}
set {
objc_setAssociatedObject(self, &ascociatedObjectPointer, myLabel, .OBJC_ASSOCIATION_RETAIN)
}
}
}
How it works ??
Simple by writing setter and getter for the variable which you are posing or pretending to be stored property and by internally holding a pointer which has nothing to do with the existing class, hence it won't affect the structure of existing class.
Hope it helps.
You can use NSMapTable like this:
extension UITextField {
private static var placeholderLabelMap: NSMapTable<UITextField, UILabel> = .weakToStrongObjects()
var placeholderLabel: UILabel? {
get {
return UITextField.placeholderLabelMap.object(forKey: self)
}
set {
UITextField.placeholderLabelMap.setObject(newValue, forKey: self)
}
}
}
The advantage of Sandeep's answer might be thread safety. You can see this Stack Overflow topic for comparison between the approaches.

Declaring a Property with Type Constraint in Swift

I need to create a custom fields framework in my app. I defined a protocol for the fields called FieldType and extended with it UITextField and UIButton to be different types of fields.
Now I want to create a container view for the fields so I want the container to be able to refer to its field elements as both UIViews and FieldTypes, and I'm wondering if there's a concise way to define the type of elements it receives to be a specific UIView that implements the FieldType protocol?
I can have FieldContainerView accept UIViews or FieldTypes and check manually that it also matches the other with a guard statement, but it feels a bit cumbersome.
I tried 2 approaches:
1) Define a Custom Intermediary FieldViewType
The idea is to have FieldViewType extend UIView directly with FieldType so it might be useful as a general case for UITextField: FieldType and UIButton: FieldType. But as this code sample clearly shows, this does not work.
protocol FieldType {
var showError: Bool { get set }
var isEmpty: Bool { get set }
}
class CustomTextField: UITextField, FieldType {}
class CustomButtonField: UIButton, FieldType {}
let textField = CustomTextField()
textField is UIView // True
textField is FieldType // True
let buttonField = CustomButtonField()
buttonField is UIView // True
buttonField is FieldType // True
class FieldView: UIView, FieldProtocol {}
let field = FieldView()
field is UIView // True
field is FieldProtocol // True
textField is FieldView // False
buttonField is FieldView // False
2) Use Generics
I can define a generic type that matches the requirements like so <FieldViewType: UIView where FieldViewType: FieldType>, but I don't see where to use to best solve my problem. If I define it at the class level
class FieldContainerView<FieldViewType: UIView where FieldViewType: FieldType>: UIView {
var fields = [FieldViewType]()
func addField(FieldViewType: field) {
fields.append(field)
}
}
I need to declare the container class once for each field type I'll want to use and won't be able to use 2 field types in the same container.
The other option is to define type constraint at the function level with addField
class FieldContainerView: UIView {
var fields = [UIView]()
func addField<FieldViewType: UIView where FieldViewType: FieldType>(FieldViewType: field) {
fields.append(field)
}
}
and then cast each element in fields to FieldType when necessary and I'll know the cast will always work because addField is the only way to add elements to the container. But this also feels too cumbersome.
It feels like the best way around this would have been to be able to define FieldViewType with a typealias, but this doesn't seem to be supported. Or have UIView be defined with a protocol so it could be mixed better, but UIKit isn't constructed in this manner.
So it seems that at the moment there's no way to create a type constraint in property declarations. I don't know why, but I don't know anything about language implementations.
I went for a workaround where FieldType also has a view: UIView property with a default implementation.
The new FieldType declaration:
protocol FieldType {
var showError: Bool { get set }
var isEmpty: Bool { get set }
var view: UIView { get }
}
extension FieldType where Self: UIView {
var view: UIView {
return self
}
}
This way it doesn't matter from which class in the UIKit hierarchy you inherited before conforming to the FieldType protocol, as long as you have UIView somewhere as your super class, you'll have an accessibly view property.
This feels like a workaround, but at least it saves dual-declarations for collections that need both the FieldType and the UIView properties of an object.
Can you change this line:
class FieldView: UIView, FieldProtocol {}
To this:
class FieldView: UIView, FieldType {}

Swift: property observers for computed properties

As far as I know, Swift allows us to set property observers for either stored and computed properties. But if computed property value depends on some backing store, property observers are not fired when these backing store values are changed:
public class BaseClass {
private var privateVar1: Int = 0
private var privateVar2: Int = 0
public var property: Int {
get {
return privateVar1 * privateVar2
}
set {
print("some setter without effect")
}
}
private func changeSomeValues() {
privateVar1 = 1
privateVar2 = 2
}
}
public class SubClass : BaseClass {
override var property: Int {
didSet {
print("didSet \(property)")
}
}
}
didSet of SubClass isn't called when changeSomeValues is called.
Let's consider a case: we have such BaseClass in a third-party framework. We define SubClass in our app. The question is: how can we rely on SubClass observers without knowledge about property nature: is it stored (and we can rely on observers) or computed (and then we can't expect firing observers each time when we expect it)? Is it possible? If no, is it an incapsulation violation?
That behaviour is perfectly normal. There is no way for the compiler to know which backing store really corresponds to which computed property. Your backing store in this case is made up of private variables that will not be accessible outside the class itself. So the only place where an "under the hood" change can occur is in the base class. It is that class's prerogative to use its calculated properties (which will trigger the observers) or the backstore (which will not).
In your example, assuming you never want to allow "invisible" changes, the changeSomeValues() function is breaking its own rules and not respecting the contract it promised to its subclasses and callers.

Resources