UIView extension weird behavior when using generics [duplicate] - swift

Is there any way to get the following working in Swift 3?
let button = UIButton().apply {
$0.setImage(UIImage(named: "UserLocation"), for: .normal)
$0.addTarget(self, action: #selector(focusUserLocation),
for: .touchUpInside)
$0.translatesAutoresizingMaskIntoConstraints = false
$0.backgroundColor = UIColor.black.withAlphaComponent(0.5)
$0.layer.cornerRadius = 5
}
The apply<T> function should take a closure of type (T)->Void, run it passing self into it, and then simply return self.
Another option would be to use an operator for this like "=>"
(borrowed the idea from Kotlin and Xtend languages).
Tried to do extension of NSObject like this:
extension NSObject {
func apply<T>(_ block: (T)->Void) -> T
{
block(self as! T)
return self as! T
}
}
But it requires explicit declaration of the parameter type in closure:
let button = UIButton().apply { (it: UIButton) in
it.setImage(UIImage(named: "UserLocation"), for: .normal)
it.addTarget(self, action: #selector(focusUserLocation),
for: .touchUpInside)
...
This is not convenient and makes the whole idea not worth the effort. The type is already specified at object creation and it should be possible not to repeat it explicitly.
Thanks!

The HasApply protocol
First of all lets define the HasApply protocol
protocol HasApply { }
and related extension
extension HasApply {
func apply(closure:(Self) -> ()) -> Self {
closure(self)
return self
}
}
Next let make NSObject conform to HasApply.
extension NSObject: HasApply { }
That's it
Let's test it
let button = UIButton().apply {
$0.titleLabel?.text = "Tap me"
}
print(button.titleLabel?.text) // Optional("Tap me")
Considerations
I wouldn't use NSObject (it's part of the Objective-C way of doing things and I assume it will be removed at some point in the future). I would prefer something like UIView instead.
extension UIView: HasApply { }

I had the same issue and ended up solving it with an operator:
infix operator <-< : AssignmentPrecedence
func <-<<T:AnyObject>(left:T, right:(T)->()) -> T
{
right(left)
return left
}
let myObject = UIButton() <-< { $0.isHidden = false }

There's a very good and simple Cocoapods library available called Then that does exactly that. Only that it uses then instead of apply. Simply import Then and then you can do as the OP asked for:
import Then
myObject.then {
$0.objectMethod()
}
let label = UILabel().then {
$0.color = ...
}
Here's how the protocol is implemented: https://github.com/devxoul/Then/blob/master/Sources/Then/Then.swift
extension Then where Self: Any {
public func then(_ block: (Self) throws -> Void) rethrows -> Self {
try block(self)
return self
}

Alain has a good answer if you're not allergic to custom operators. If you'd rather not use those, the best alternative I could come up with was:
#discardableResult func apply<T>(_ it:T, f:(T)->()) -> T {
f(it)
return it
}
which then allows you to use:
let button = apply(UIButton()) { $0.setTitleText("Button") }
It's not quite the same, but works out pretty well in general and has the advantage that T is completely unrestrained. It's an obviously contrived example, but:
func apply<T,R>(_ it:T, f:(T)->R) -> R {
return f(it)
}
even allows:
print("\(apply(32) { $0 + 4 })")

In case of object which must be created with non optional init:
let button = UIButton()
Optional(button).map {
$0.isEnabled = true
}
In case of object which must be created with optional init:
let button = UIButton(coder: coder)
button.map {
$0.isEnabled = true
}
In case of existing optional object we can use map function:
optionalObject.map {
$0.property1 = true
$0.property2 = true
}
If object must be cast before being use:
(optionalObject as? NewType).mapĀ {
$0.property1 = true
$0.property2 = true
}

Related

Aggregating an array of swift types

In Swift, I know that types are classes unto themselves, but I am having a hard time making this work.
I am trying to create a "factory," where I have an Array of types, each of which conform to a protocol (so it would actually be an Array of protocol), which specifies a static "factory" function.
This would allow me to associate a set of classes with an instance, so that the instance can cycle through the "factory," and instantiate the classes that are associated with that instance.
Here's what I mean:
protocol A {
static func makeAStat() -> A
func makeADyn() -> A
}
struct APrime: A {
static func makeAStat() -> A {
return Self()
}
func makeADyn() -> A {
return Self.makeAStat()
}
}
let instanceOne = APrime.makeAStat()
let instanceTwo = APrime().makeADyn()
let arrayOf1 = [APrime()]
let instance3 = arrayOf1[0].makeADyn()
//let arrayOf2: [A.Type] = [APrime]
//let instance4 = arrayOf2[0].makeAStat()
Note the two commented-out lines. They will break the playground, but I'd like to get something like that working, so I can just say something like "This is a WidgetPlus object, so it gets to pick from these three types as handlers."
Does anyone have a suggestion as to the best way for me to achieve this?
Just add .self
let arrayOf2: [A.Type] = [APrime.self]
let instance4 = arrayOf2[0].makeAStat()
And for more clarity I'd recommend to write
protocol A {
static func makeAStat() -> Self
func makeADyn() -> Self
}
struct APrime: A {
static func makeAStat() -> APrime {
return APrime()
}
func makeADyn() -> APrime {
return APrime.makeAStat()
}
}

Swift UITextField target-actions as closure, problem with not removing target-actions

I have such code a little modified from code of Eric Armstrong
Adding a closure as target to a UIButton
But there is the problem with both codes. Those from Eric does remove all target-actions on
func removeTarget(for controlEvent: UIControl.Event = .touchUpInside)
And modified code on the other hand do not remove target-actions at all. Of course it is caused by if condition, but it also means that there are no targets stored properly in Storable property.
extension UIControl: ExtensionPropertyStorable {
class Property: PropertyProvider {
static var property = NSMutableDictionary()
static func makeProperty() -> NSMutableDictionary? {
return NSMutableDictionary()
}
}
func addTarget(for controlEvent: UIControl.Event = .touchUpInside, target: #escaping (_ sender: Any) ->()) {
let key = String(describing: controlEvent)
let target = Target(target: target)
addTarget(target, action: target.action, for: controlEvent)
property[key] = target
}
func removeTarget(for controlEvent: UIControl.Event = .touchUpInside) {
let key = String(describing: controlEvent)
if let target = property[key] as? Target {
removeTarget(target, action: target.action, for: controlEvent)
property[key] = nil
}
}
}
// Wrapper class for the selector
class Target {
private let t: (_ sender: Any) -> ()
init(target t: #escaping (_ sender: Any) -> ()) { self.t = t }
#objc private func s(_ sender: Any) { t(sender) }
public var action: Selector {
return #selector(s(_:))
}
}
// Protocols with associatedtypes so we can hide the objc_ code
protocol PropertyProvider {
associatedtype PropertyType: Any
static var property: PropertyType { get set }
static func makeProperty() -> PropertyType?
}
extension PropertyProvider {
static func makeProperty() -> PropertyType? {
return nil
}
}
protocol ExtensionPropertyStorable: class {
associatedtype Property: PropertyProvider
}
// Extension to make the property default and available
extension ExtensionPropertyStorable {
typealias Storable = Property.PropertyType
var property: Storable {
get {
let key = String(describing: type(of: Storable.self))
guard let obj = objc_getAssociatedObject(self, key) as? Storable else {
if let property = Property.makeProperty() {
objc_setAssociatedObject(self, key, property, .OBJC_ASSOCIATION_RETAIN)
}
return objc_getAssociatedObject(self, key) as? Storable ?? Property.property
}
return obj
}
set {
let key = String(describing: type(of: Storable.self))
return objc_setAssociatedObject(self, key, newValue, .OBJC_ASSOCIATION_RETAIN) }
}
}
My aim is to precisely register target-actions with closures and remove them without removing all other target-actions added to given UITextField via #selector. Now I can have removed ALL or NONE of target-actions while using this approach for closure-style target actions.
UPDATE
Based on Eric Armstrong answer i have implemented my version.
But what I have experienced in version proposed by Eric was that when adding target actions to TextField on TableView list while cells appear and then removing this target actions from Text Fields while cells diseappear the previous code seems to remove all target actions on removeTarget(for:) exection. So when in other place in code like UITableViewCell I have added additional target action on totaly different target (UITableViewCell object, not this custom Target() objects) while cells was disappearing and then again appearing on screen and removeTarget(for) was executed then this other (external as I call them target actions) also was removed and never called again.
I consider that some problem was usage of [String: Target] dictionary which is value type and it was used in case of property getter in objc_getAssociatedObject where there was
objc_getAssociatedObject(self, key) as? Storable ?? Property.property
So as I understand it then there wasn't objc object for given key and Storable was nil and nil-coalescing operator was called and static value type Property.property return aka [String : Dictionary]
So it was returned by copy and Target object was stored in this copied object which wasn't permanently stored and accessed in removeTarget(for:) always as nil. So nil was passed to UIControl.removetTarget() and all target actions was always cleared!.
I have tried simple replacing [String: Target] Swift dictionary with NSMutableDictionary which is a reference type so I assume it can be stored. But this simple replacement for static variable and just returning it via nil-coalesing operator caused as I assume that there as only one such storage for Target objects and then while scrolling Table View each removeForTarget() has somehow remove all target actions from all UITextFields not only from current.
I also consider usage of String(describing: type(of: Storable.self)) as being wrong as it will be always the same for given Storable type.
Ok, I think I finally solved this issue
The main problem was usage of AssociatedKey! it needs to be done like below
https://stackoverflow.com/a/48731142/4415642
So I ended up with such code:
import UIKit
/**
* Swift 4.2 for UIControl and UIGestureRecognizer,
* and and remove targets through swift extension
* stored property paradigm.
* https://stackoverflow.com/a/52796515/4415642
**/
extension UIControl: ExtensionPropertyStorable {
class Property: PropertyProvider {
static var property = NSMutableDictionary()
static func makeProperty() -> NSMutableDictionary? {
return NSMutableDictionary()
}
}
func addTarget(for controlEvent: UIControl.Event = .touchUpInside, target: #escaping (_ sender: Any) ->()) {
let key = String(describing: controlEvent)
let target = Target(target: target)
addTarget(target, action: target.action, for: controlEvent)
property[key] = target
print("ADDED \(ObjectIdentifier(target)), \(target.action)")
}
func removeTarget(for controlEvent: UIControl.Event = .touchUpInside) {
let key = String(describing: controlEvent)
if let target = property[key] as? Target {
print("REMOVE \(ObjectIdentifier(target)), \(target.action)")
removeTarget(target, action: target.action, for: controlEvent)
property[key] = nil
}
}
}
extension UIGestureRecognizer: ExtensionPropertyStorable {
class Property: PropertyProvider {
static var property: Target?
}
func addTarget(target: #escaping (Any) -> ()) {
let target = Target(target: target)
addTarget(target, action: target.action)
property = target
}
func removeTarget() {
let target = property
removeTarget(target, action: target?.action)
property = nil
}
}
// Wrapper class for the selector
class Target {
private let t: (_ sender: Any) -> ()
init(target t: #escaping (_ sender: Any) -> ()) { self.t = t }
#objc private func s(_ sender: Any) { t(sender) }
public var action: Selector {
return #selector(s(_:))
}
deinit {
print("Deinit target: \(ObjectIdentifier(self))")
}
}
// Protocols with associatedtypes so we can hide the objc_ code
protocol PropertyProvider {
associatedtype PropertyType: Any
static var property: PropertyType { get set }
static func makeProperty() -> PropertyType?
}
extension PropertyProvider {
static func makeProperty() -> PropertyType? {
return nil
}
}
protocol ExtensionPropertyStorable: class {
associatedtype Property: PropertyProvider
}
// Extension to make the property default and available
extension ExtensionPropertyStorable {
typealias Storable = Property.PropertyType
var property: Storable {
get {
guard let obj = objc_getAssociatedObject(self, &AssociatedKeys.property) as? Storable else {
if let property = Property.makeProperty() {
objc_setAssociatedObject(self, &AssociatedKeys.property, property, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
return objc_getAssociatedObject(self, &AssociatedKeys.property) as? Storable ?? Property.property
}
return obj
}
set {
return objc_setAssociatedObject(self, &AssociatedKeys.property, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
}
private struct AssociatedKeys {
static var property = "AssociatedKeys.property"
}

Self type as argument in closure

Is it possible for an instance of a UIView to call a method which executes a closure, and inside that closure referring to the same instance? This is the non-generic version:
import UIKit
public extension UIView {
func layout(from: (UIView) -> ()) {
from(self)
}
}
When I call it with a UILabel for example, I do not have access to e.g. the text aligment. Is it possible that inside the closure I can refer to the UILabel? I would expect something like this would work:
func layout(from: (Self) -> ()) {
from(self)
}
But it doesn't compile. Is there a workaround? This is what I want:
let label = UILabel(frame: .zero)
label.layout { $0.textAlignment = .natural } // Currenly not working, since $0 = UIView.
Different approach: Protocol Extension with associated type.
protocol Layout {
associatedtype View : UIView = Self
func layout(from: (View) -> ())
}
extension Layout where Self : UIView {
func layout(from: (Self) -> ()) {
from(self)
}
}
extension UIView : Layout {}
let label = UILabel(frame: .zero)
label.layout { $0.textAlignment = .natural }
There are different ways to do it.
Firstly, you could use the closures' variable capturing system in order to directly use the variable inside the closure, without passing it as an argument.
public extension UIView {
func layout(from: () -> ()) {
from()
}
}
label.layout { label.textAlignment = .natural }
Otherwise, if you want to pass a generic UIView and change the behaviour accordingly to the specific one - since it looks like you know for sure what type you are working on - you can use a downcast:
public extension UIView {
func layout(from: (UIView) -> ()) {
from(self)
}
}
let label = UILabel(frame: .zero)
label.layout { ($0 as! UILabel).textAlignment = .natural }
Anyway, why are you doing:
label.layout { $0.textAlignment = .natural }
instead of:
label.textAlignment = .natural
Is there any particular reason not to do it? I imagine there's something bigger behind the scenes, I'm just curious.

How can you specify a generic based on the type an extension is applied to? [duplicate]

I'm looking to be able to pull out an instance of a UIView subclass from a Nib.
I'd like to be able to call MyCustomView.instantiateFromNib() and have an instance of MyCustomView. I'm almost ready to just port the working Objective-C code I have via the bridging header, but figured I'd try the idiomatic approach first. That was two hours ago.
extension UIView {
class func instantiateFromNib() -> Self? {
let topLevelObjects = NSBundle.mainBundle().loadNibNamed("CustomViews", owner: nil, options: nil)
for topLevelObject in topLevelObjects {
if (topLevelObject is self) {
return topLevelObject
}
}
return nil
}
}
Now if (topLevelObject is self) { is wrong because "Expected type after 'is'". What I've tried after that shows a lot about what I don't understand about the Swift type system.
if (topLevelObject is Self) {
if (topLevelObject is self.dynamicType) {
if (topLevelObject is self.self) {
A million other variations that are not even wrong.
Any insight is appreciated.
Using the approach from How can I create instances of managed object subclasses in a NSManagedObject Swift extension?
you can define a generic helper method which infers the type of self from the calling context:
extension UIView {
class func instantiateFromNib() -> Self? {
return instantiateFromNibHelper()
}
private class func instantiateFromNibHelper<T>() -> T? {
let topLevelObjects = NSBundle.mainBundle().loadNibNamed("CustomViews", owner: nil, options: nil)
for topLevelObject in topLevelObjects {
if let object = topLevelObject as? T {
return object
}
}
return nil
}
}
This compiles and works as expected in my quick test. If
MyCustomView is your UIView subclass then
if let customView = MyCustomView.instantiateFromNib() {
// `customView` is a `MyCustomView`
// ...
} else {
// Not found in Nib file
}
gives you an instance of MyCustomView, and the type is
inferred automatically.
Update for Swift 3:
extension UIView {
class func instantiateFromNib() -> Self? {
return instantiateFromNibHelper()
}
private class func instantiateFromNibHelper<T>() -> T? {
if let topLevelObjects = Bundle.main.loadNibNamed("CustomViews", owner: nil, options: nil) {
for topLevelObject in topLevelObjects {
if let object = topLevelObject as? T {
return object
}
}
}
return nil
}
}
I believe the conditional expression you're looking for is topLevelObject.dynamicType == self
Combining this with unsafeBitCast (which, by Apple's own documentation, "Breaks the guarantees of Swift's type system"), we can forcefully downcast topLevelObject to self's type. This should be safe because we already made sure that topLevelObject is the same type as self
This is one way to get around the helper method using generics that Martin R described.
extension UIView {
class func instantiateFromNib() -> Self? {
let bundle = NSBundle.mainBundle().loadNibNamed("CustomViews", owner: nil, options: nil)
for topLevelObject in topLevelObjects {
if topLevelObject.dynamicType == self {
return unsafeBitCast(topLevelObject, self)
}
}
return nil
}
}
Note that Apple also says in their documentation for unsafeBitCast:
There's almost always a better way to do anything.
So be careful!

Using 'self' in class extension functions in Swift

I'm looking to be able to pull out an instance of a UIView subclass from a Nib.
I'd like to be able to call MyCustomView.instantiateFromNib() and have an instance of MyCustomView. I'm almost ready to just port the working Objective-C code I have via the bridging header, but figured I'd try the idiomatic approach first. That was two hours ago.
extension UIView {
class func instantiateFromNib() -> Self? {
let topLevelObjects = NSBundle.mainBundle().loadNibNamed("CustomViews", owner: nil, options: nil)
for topLevelObject in topLevelObjects {
if (topLevelObject is self) {
return topLevelObject
}
}
return nil
}
}
Now if (topLevelObject is self) { is wrong because "Expected type after 'is'". What I've tried after that shows a lot about what I don't understand about the Swift type system.
if (topLevelObject is Self) {
if (topLevelObject is self.dynamicType) {
if (topLevelObject is self.self) {
A million other variations that are not even wrong.
Any insight is appreciated.
Using the approach from How can I create instances of managed object subclasses in a NSManagedObject Swift extension?
you can define a generic helper method which infers the type of self from the calling context:
extension UIView {
class func instantiateFromNib() -> Self? {
return instantiateFromNibHelper()
}
private class func instantiateFromNibHelper<T>() -> T? {
let topLevelObjects = NSBundle.mainBundle().loadNibNamed("CustomViews", owner: nil, options: nil)
for topLevelObject in topLevelObjects {
if let object = topLevelObject as? T {
return object
}
}
return nil
}
}
This compiles and works as expected in my quick test. If
MyCustomView is your UIView subclass then
if let customView = MyCustomView.instantiateFromNib() {
// `customView` is a `MyCustomView`
// ...
} else {
// Not found in Nib file
}
gives you an instance of MyCustomView, and the type is
inferred automatically.
Update for Swift 3:
extension UIView {
class func instantiateFromNib() -> Self? {
return instantiateFromNibHelper()
}
private class func instantiateFromNibHelper<T>() -> T? {
if let topLevelObjects = Bundle.main.loadNibNamed("CustomViews", owner: nil, options: nil) {
for topLevelObject in topLevelObjects {
if let object = topLevelObject as? T {
return object
}
}
}
return nil
}
}
I believe the conditional expression you're looking for is topLevelObject.dynamicType == self
Combining this with unsafeBitCast (which, by Apple's own documentation, "Breaks the guarantees of Swift's type system"), we can forcefully downcast topLevelObject to self's type. This should be safe because we already made sure that topLevelObject is the same type as self
This is one way to get around the helper method using generics that Martin R described.
extension UIView {
class func instantiateFromNib() -> Self? {
let bundle = NSBundle.mainBundle().loadNibNamed("CustomViews", owner: nil, options: nil)
for topLevelObject in topLevelObjects {
if topLevelObject.dynamicType == self {
return unsafeBitCast(topLevelObject, self)
}
}
return nil
}
}
Note that Apple also says in their documentation for unsafeBitCast:
There's almost always a better way to do anything.
So be careful!