Created
January 18, 2019 09:05
-
-
Save felginep/0148b40e26b19d07e81c2e1e4f2ff3d2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
@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)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is a very good idea @rbresjer, we would also have to return inside the static property, something like:
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:
So you can do: