Generi c UIView Инициализатор с закрытием в Swift - PullRequest
2 голосов
/ 11 апреля 2020

Я хочу написать обобщенный c инициализатор UIView, чтобы я мог инициализировать UIViews, передав закрытие конфигурации в initializer.What я хочу, чтобы приведенный ниже синтаксис работал для всех подклассов UIView.

let button = UIButton() {
    $0.backgroundColor = UIColor.red
    $0.frame = CGRect(x: 220, y: 30, width: 100, height: 100)
    $0.setTitle("Test", for: .normal)
}

Я написал удобный инициализатор в расширении UIView , но с этим я не могу установить свойства подклассов UIView, такие как свойство setTitle (_: for) для UIButton , потому что при закрытии он всегда отправляет параметр типа UIView вместо специфика c тип подкласса.

Вот мой инициализатор.

extension UIView {

    convenience init<T: UIView>(_ configurations: (T) -> Void) {
        self.init()

        configurations(self as! T)
    }
}

Любые предложения будут оценены.

ПРИМЕЧАНИЕ: Я был в состоянии достигнуть вышеупомянутого поведения инициализации UIView и подтипов с закрытием, используя протокол, но мне интересно, может ли это быть достигнуто таким образом, т.е. написание удобного инициализатора в расширении UIView без какого-либо дополнительного протокола.

Ответы [ 2 ]

1 голос
/ 12 апреля 2020

То, о чем вы спрашиваете, не является тривиальным использованием UIKit. В SwiftUI стили представлений являются декларативными, но с использованием шаблона Builder, где каждый модификатор представления возвращает представление, чтобы вы могли объединить настройки. Тем не менее, вы специально спросили о возможности настройки представлений, передав замыкание с, см. Решение 2 ниже. Но сначала я хотел представить другой подход, используя ArrayLiterals с перечислениями, см. Решение 1.

Решение 1 - Представление Composer

Представление Composer является библиотека (которую я разработал несколько лет go) для объявления представлений с использованием литералов массива перечислений, которая позволяет объявлять представления следующим образом:

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 и вы увидите список поддерживаемых представлений , большинство классов представлений поддерживаются.

Принцип работы View Composer слишком сложен для публикации здесь, но взгляните на код!

Решение 2 - Zhip

В моем приложении с открытым исходным кодом iOS Zilliqa Wallet под названием Zhip Я создал еще одно решение для простой настройки UIViews, очень похожее на ваш вопрос.

Вот здесь ReceiveView , который выглядит следующим образом

zhip_receive

Имея этот код:

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 через конфигурацию некоторых представлений:

Конфигурируем UILabel с именем addressTitleLabel с помощью этого кода :

addressTitleLabel.withStyle(.title) {
    $0.text(€.Label.myPublicAddress)
}
  1. - это просто локальные typealias для контекста локализации для переведенных строк с ключом L10n.Scene.Receive.Label.myPublicAddress, поэтому для устройства iOS с Engli sh настройка языка, которая будет переводить в строку "My public address".

  2. .withStyle(.title) - это вызов функции с именем withStyle, которую я объявил в UILabel, см. код на 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
}

Мы передаем .title в качестве аргумента функции, и в объявлении функции выше вы можете видеть, что типом является UILabel.Style, что означает, что мы объявили переменную stati c с именем title как расширение на UILabel.Style, являющееся неким стилем по умолчанию. Это несколько похоже на Шрифт SwiftUI, в котором есть регистр enum, называемый title (но я создал его задолго до выхода SwiftUI ?). Где в случае SwiftUI это предустановка Font, где, как и в моем случае, это предустановка всего UILabel.Style. Давайте посмотрим на это!

UILabel.Style, здесь title:

static var title: UILabel.Style {
    return UILabel.Style(
        font: UIFont.title
    )
}

Так что это просто вызов инициализатора со значением UIFont.title в качестве шрифта.

Настроить блок - в функции withStyle вторым аргументом является замыкающее замыкание, являющееся замыканием для настройки предустановленного стиля. В случае addressTitleLabel здесь мы устанавливаем свойство text для UILabel.

Мульти-настройка в замыкании - в другом представлении - UnlockAppWithPincodeView мы выполняем несколько настроек UILabel:

func setupSubviews() {
    descriptionLabel.withStyle(.body) {
        $0
            .text(€.label)
            .textAlignment(.center)
    }
}
Как применяются стили: Ранее выше мы видели код для withStyle, который вызывает apply(style: style) перед возвратом представления (UILabel). Это где наш взгляд получает стиль. Что делает именно то, что вы ожидаете, он применяет каждую конфигурацию:
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 lib я приложил еще больше усилий для создания протоколов мостов и т. Д. c, но это также привело к гораздо более сложной базе кода). Пожалуйста, взгляните на каталог в Zhip, где находится весь этот код - ../Source/Extensions/UIKit.

Особенно благодаря предварительным настройкам stati c для каждого стиля этого приводит к довольно аккуратному и короткому синтаксису для создания и оформления UIViews.

1 голос
/ 11 апреля 2020

На самом деле проблема общая c 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 generi c UIButton.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...