-
-
Save felginep/0148b40e26b19d07e81c2e1e4f2ff3d2 to your computer and use it in GitHub Desktop.
import Foundation | |
import UIKit | |
struct ViewStyle<T> { | |
let style: (T) -> Void | |
} | |
let filled = ViewStyle<UIButton> { | |
$0.setTitleColor(.white, for: .normal) | |
$0.backgroundColor = .red | |
} | |
let rounded = ViewStyle<UIButton> { | |
$0.layer.cornerRadius = 4.0 | |
} | |
extension ViewStyle { | |
func compose(with style: ViewStyle<T>) -> ViewStyle<T> { | |
return ViewStyle<T> { | |
self.style($0) | |
style.style($0) | |
} | |
} | |
} | |
let roundedAndFilled = rounded.compose(with: filled) | |
extension ViewStyle where T: UIButton { | |
static var filled: ViewStyle<UIButton> { | |
return ViewStyle<UIButton> { | |
$0.setTitleColor(.white, for: .normal) | |
$0.backgroundColor = .red | |
} | |
} | |
static var rounded: ViewStyle<UIButton> { | |
return ViewStyle<UIButton> { | |
$0.layer.cornerRadius = 4.0 | |
} | |
} | |
static var roundedAndFilled: ViewStyle<UIButton> { | |
return rounded.compose(with: filled) | |
} | |
} | |
func style<T>(_ object: T, with style: ViewStyle<T>) { | |
style.style(object) | |
} | |
protocol Stylable { | |
init() | |
} | |
extension UIView: Stylable {} | |
extension Stylable { | |
init(style: ViewStyle<Self>) { | |
self.init() | |
apply(style) | |
} | |
func apply(_ style: ViewStyle<Self>) { | |
style.style(self) | |
} | |
} | |
let button = UIButton(style: .roundedAndFilled) | |
button.setTitle("My Button", for: .normal) | |
button.sizeToFit() | |
button |
This is a very good idea @rbresjer, we would also have to return inside the static property, something like:
static var rounded: ViewStyle<UIButton> {
return ViewStyle<UIButton> {
$0.layer.cornerRadius = 4.0
return $0
}
}
static var fullyRounded: ViewStyle<UIButton> {
return ViewStyle<UIButton> {
$0.layer.cornerRadius = $0.frame.height / 2
return $0
}
}
I would also add a ready method without any return inside the Stylable extension, or something on these lines so it will stop complaining that the return is not being used:
extension Stylable {
func apply(_ style: ViewStyle<Self>) -> Self {
return style.style(self)
}
func ready() {}
}
So you can do:
class ViewController: UIViewController {
@IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
button
.apply(.filled)
.apply(.fullyRounded)
.ready()
}
}
@erickva I think you can use @discardableResult for that. In that way you wouldn't need to add a ready
function, which doesn't add any value.
Also, there's no need to import both Foundation and UIKit. UIKit is enough. Read this for more info:
https://stackoverflow.com/a/30181218
What if I want to parameterize the ViewStyle<UIButton>.rounded
style? Couldn't functions be used as well?
static func rounded(_ radius: Double) -> ViewStyle<UIButton> {
return ViewStyle<UIButton> {
$0.layer.cornerRadius = radius
return $0
}
}
Resulting in:
class ViewController: UIViewController {
@IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
button
.apply(.filled)
.apply(.rounded(10.0))
}
}
I've built something similar in the past:
https://gist.github.com/IanKeen/ac051da0eeeaefcae9cbb8c1e4bb9c1b
You might like the styleAll
variant which uses the appearance proxy
How could you allow composing ViewStyles for UIView subclasses with ViewStyles for UIView?
extension ViewStyle {
static var buttonStyle: ViewStyle<UIButton> {
return ViewStyle<UIButton> {
$0.titleLabel?.font = UIFont.systemFont(ofSize: 15)
}
}
static var roundedAndFilledButton: ViewStyle<UIButton> {
// This doesn't work because 'roundedAndFilled' returns a 'ViewStyle<UIView>' and buttonStyle is expecting 'ViewStyle<UIButton>'
return buttonStyle.compose(with: .roundedAndFilled)
}
}
I like the idea of overloading the +
operator
extension ViewStyle {
static func +(left: ViewStyle<T>, right: ViewStyle<T>) -> ViewStyle<T> {
return ViewStyle<T> {
left.style($0)
right.style($0)
return $0
}
}
}
So then you can do
let button = UIButton(style: rounded + filled)
Nice idea!
If you would return ‘self’ from the ‘style:’ method, wouldn’t that make composing easier? I’m on mobile now so can’t verify, but e.g.:
‘‘‘
struct ViewStyle {
let style: (T) -> T
}
extension Stylable {
init(style: ViewStyle) {
self.init()
apply(style)
}
func apply(_ style: ViewStyle) -> Self {
return style.style(self)
}
}
// Then you could do e.g.:
button
.apply(.rounded)
.apply(.filled)
‘‘‘
That would remove the need for the compose method, as well as the need for predefined composed styles.