[英]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.