[英]Generic UIView Initializer with closure in swift
我想編寫一個通用的 UIView 初始化程序,這樣我就可以通過在初始化程序中傳遞配置閉包來初始化 UIViews。我想要的是下面的語法適用於所有 UIView 子類。
let button = UIButton() {
$0.backgroundColor = UIColor.red
$0.frame = CGRect(x: 220, y: 30, width: 100, height: 100)
$0.setTitle("Test", for: .normal)
}
I have written the convenience initializer in UIView extension but with this I am not able to set UIView subclasses properties like the setTitle(_:for) property for UIButton because in closure it is always sending UIView type parameter instead of the specific subclass type.
這是我的初始化程序。
extension UIView {
convenience init<T: UIView>(_ configurations: (T) -> Void) {
self.init()
configurations(self as! T)
}
}
任何建議將不勝感激。
注意:我能夠實現上述使用協議初始化 UIView 和帶有閉包的子類型的行為,但我想知道這是否可以通過這種方式實現,即在 UIView 擴展中編寫便利初始化程序而無需任何附加協議。
實際上問題是通用T
沒有解決為UIButton
。 您沒有指定configuration
閉包的參數類型是什么。
let button = UIButton() { (b: UIButton) in
b.backgroundColor = UIColor.red
b.frame = CGRect(x: 220, y: 30, width: 100, height: 100)
b.setTitle("Test", for: .normal)
}
現在將看到T
泛型UIButton
。
使用 UIKit,您提出的問題並非易事。 在 SwiftUI 中,視圖樣式是聲明性的,但使用 Builder 模式,其中每個視圖修飾符都返回視圖,以便您可以鏈接自定義。 但是,您特別詢問是否能夠通過傳遞閉包來自定義視圖,請參閱下面的解決方案 2 。 但首先我想提出一種不同的方法,將 ArrayLiterals 與枚舉一起使用,請參閱解決方案 1。
ViewComposer是一個庫(我幾年前開發的),用於使用枚舉的數組文字聲明視圖,它允許您像這樣聲明視圖:
let button: UIButton = [.color(.red), .text("Red"), .textColor(.blue)]
let label: UILabel = [.text("Hello World"), .textColor(.red)]
lazy var emailField: UITextField = [.font(.big), .height(50), .placeholder("Email"), .delegate(self)]
向下滾動到README 的底部,您將看到支持的視圖列表,支持大多數視圖類。
ViewComposer 的工作原理有點復雜,無法在此處發布,但請查看代碼!
在我的開源 iOS Zilliqa 錢包應用程序中,我創建了另一個用於輕松配置 UIViews 的解決方案,與您的問題非常相似。
這里是ReceiveView ,看起來像這樣
有這個代碼:
final class ReceiveView: ScrollableStackViewOwner {
private lazy var qrImageView = UIImageView()
private lazy var addressTitleLabel = UILabel()
private lazy var addressValueTextView = UITextView()
private lazy var copyMyAddressButton = UIButton()
private lazy var addressAndCopyButton = UIStackView(arrangedSubviews: [addressValueTextView, copyMyAddressButton])
private lazy var addressViews = UIStackView(arrangedSubviews: [addressTitleLabel, addressAndCopyButton])
private lazy var requestingAmountField = FloatingLabelTextField()
private lazy var requestPaymentButton = UIButton()
// MARK: - StackViewStyling
lazy var stackViewStyle = UIStackView.Style([
qrImageView,
addressViews,
requestingAmountField,
.spacer,
requestPaymentButton
])
override func setup() {
setupSubviews()
}
}
和視圖配置:
private typealias € = L10n.Scene.Receive
private extension ReceiveView {
// swiftlint:disable:next function_body_length
func setupSubviews() {
qrImageView.withStyle(.default)
addressTitleLabel.withStyle(.title) {
$0.text(€.Label.myPublicAddress)
}
addressValueTextView.withStyle(.init(
font: UIFont.Label.body,
isEditable: false,
isScrollEnabled: false,
// UILabel and UITextView horizontal alignment differs, change inset: stackoverflow.com/a/45113744/1311272
contentInset: UIEdgeInsets(top: 0, left: -5, bottom: 0, right: -5)
)
)
copyMyAddressButton.withStyle(.title(€.Button.copyMyAddress))
copyMyAddressButton.setHugging(.required, for: .horizontal)
addressAndCopyButton.withStyle(.horizontal)
addressViews.withStyle(.default) {
$0.layoutMargins(.zero)
}
requestingAmountField.withStyle(.decimal)
requestPaymentButton.withStyle(.primary) {
$0.title(€.Button.requestPayment)
}
}
}
讓我們 go 通過 config 的一些視圖:
我們使用以下代碼配置一個名為addressTitleLabel
的UILabel
:
addressTitleLabel.withStyle(.title) {
$0.text(€.Label.myPublicAddress)
}
€
只是具有鍵L10n.Scene.Receive.Label.myPublicAddress
的已翻譯字符串的本地化上下文的本地類型別名,因此對於具有英語語言設置的 iOS 設備,將轉換為字符串"My public address"
。
.withStyle(.title)
是對我在UILabel
上聲明的名為withStyle
的 function 的調用,請參見此處 Github 上的代碼,即:
@discardableResult
func withStyle(
_ style: UILabel.Style,
customize: ((UILabel.Style) -> UILabel.Style)? = nil
) -> UILabel {
translatesAutoresizingMaskIntoConstraints = false
let style = customize?(style) ?? style
apply(style: style)
return self
}
We pass .title
as an argument to the function, and in the function declaration above you can see that the type is UILabel.Style
, meaning we have declared a static variable called title
as an extension on UILabel.Style
, being some default style. 這有點類似於SwiftUI 的 Font,它有一個名為title
的枚舉案例(但我在SwiftUI
發布之前很久就創建了這個)。 在 SwiftUI 案例中,它是Font
的預設,而在我的案例中,它是整個UILabel.Style
的預設。 讓我們來看看吧!
UILabel.Style
, 這是title
:
static var title: UILabel.Style {
return UILabel.Style(
font: UIFont.title
)
}
所以它只是用值UIFont.title
作為字體調用初始化程序。
自定義塊 - 在withStyle
function 中,第二個參數是尾隨閉包,是自定義預設樣式的閉包。 對於addressTitleLabel
,我們在這里設置UILabel
的text
屬性。
閉包中的多重定制 - 在另一個視圖中 - UnlockAppWithPincodeView
我們執行UILabel
的多重定制:
func setupSubviews() {
descriptionLabel.withStyle(.body) {
$0
.text(€.label)
.textAlignment(.center)
}
}
withStyle
的代碼,它在返回視圖 (UILabel) 之前調用apply(style: style)
)。 這是我們的視圖樣式化的地方。 這正是您所期望的,它適用於每個配置:extension UILabel {
func apply(style: Style) {
text = style.text
font = style.font ?? UIFont.Label.body
textColor = style.textColor ?? .defaultText
numberOfLines = style.numberOfLines ?? 1
textAlignment = style.textAlignment ?? .left
backgroundColor = style.backgroundColor ?? .clear
if let minimumScaleFactor = style.adjustsFontSizeMinimumScaleFactor {
adjustsFontSizeToFitWidth = true
self.minimumScaleFactor = minimumScaleFactor
}
}
}
然后,我將這個模式應用於我想支持的每個 UIKit 視圖。 這肯定是一些樣板復制粘貼(在上面提到的ViewComposer
庫中,我付出了更多努力來創建橋接協議等,但這也導致了更復雜的代碼庫)。 請查看 Zhip 中放置所有這些代碼的目錄 - ../Source/Extensions/UIKit
。
特別感謝 static 對每種樣式的預設,這為 UIViews 的創建和樣式提供了非常簡潔和簡短的語法。
像這樣簡單的事情怎么樣?
@discardableResult
func config<T>(_ object: T,
_ block (inout T) throws -> Void) rethrows -> T {
var object = object
try block(&object)
return object
}
像這樣使用它:
let view = config(UIView()) {
$0.backgroundColor = .white
$0.frame.size = .init(width: 50, height: 50)
// ... other configuration code ...
}
它看起來像一個惰性初始化器,但事實並非如此。 這是一個直接定義(您可以使用let
)。 如果您需要在代碼中引用任何其他屬性,那么您可能需要將其更改為lazy var
賦值,但是lazy var
是一個變異聲明有一點副作用。
塊參數的inout
允許它不僅適用於引用類型,也適用於值類型。
我一直在我的代碼中使用它,我很驚訝沒有人用這樣的解決方案來回答這個問題。
我越接近你想要做的就是:
public extension UIView {
convenience init(_ closure: (Self) -> Void) {
self.init()
closure(self)
}
}
簡單地與
UIView { $0.backgroundColor = .red }
主要問題是不適用於 UIView 子類,例如
UIImageView { $0.image = UIImage(named: "whatever") }
它無法編譯,錯誤是value of type Self has no member 'image'
這只是通過使用初始化程序,我認為我們遇到了 Swift 編譯器的限制。
但是,您可以嘗試以下解決方法:
public protocol WithPropertyAssignment {}
public extension WithPropertyAssignment where Self: AnyObject {
func with(_ closure: @escaping (Self) -> Void) -> Self {
closure(self)
return self
}
}
extension NSObject: WithPropertyAssignment {}
然后你可以做
UILabel().with { $0.text = "Ciao" }
UIImage().with { $0.image = UIImage() }
這不太一樣,但我猜它仍然可讀......
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.