DeclarativeLayoutKit

用于快速 UI 布局的声明式且类型安全的框架

优势
🚀 使用属性链快速配置视图
🌈 使用最简单的 DSL轻松进行布局
🎁 用于 UIStackViewextra_most_usefull 助手
🧩 可扩展性!

概述

要求

用法

🚀 属性链

使用 Sourcery 驱动的代码生成,**所有可变属性**都以函数形式表示。

let myLabel = UILabel()
    .numberOfLines(0)
    .text("Hello buddy")
    .backgroundColor(.blue)
    .isHighlighted(true)
    .borderWidth(1)
    .borderColor(.cyan)

目前,以下类型支持属性链:UIViewUIControlUILabelUIImageViewUIScrollViewUITextViewUITableViewUICollectionViewUITextFieldUIButtonUISliderUISwitchUIStackView

您还可以轻松地为其他类型生成函数 – 查看方法

😋 一些额外的语法糖

可赋值给变量

class ViewController: UIViewController {
    weak var myLabel: UILabel!

    override func loadView() {
        ...

        view.addSubview(
            UILabel()
                .numberOfLines(0)
                .text("Voila")
                // sets a reference of object that calls function(in this case, created UILabel instance) to passed variable
                .assign(to: &myLabel)
        )

        ...
    }
}

基于闭包的动作和手势

UIControl()
    .addAction(for: .valueChanged, { print("value changed") })

UIButton()
    .title("Tap me")
    .onTap({ print("didTap") }) // wrap .addAction(for: .touchUpInside, { .. })

UIView()
    .onTapGesture({ print("Kek") })
    .onLongTapGesture({ print("Cheburek") })

// ⚠️ Don't forget about ARC when use some parent view in action closure, to prevent retain cycle

👨‍👨‍👦‍👦 UIStackView extra_most_usefull 助手

预配置的堆栈初始化器

HorizontalStack([...]) // axis = .horizontal

HorizontalCenterStack([...]) // axis = .horizontal; alignment = .center

TopStack([...]) // axis = .horizontal; alignment = .top

BottomStack([...]) // axis = .horizontal; alignment = .bottom


VerticalStack([...]) // axis = .vertical

VerticalCenterStack([...]) // axis = .vertical; alignment = .center

LeftStack([...]) // axis = .vertical; alignment = .leading

RightStack([...]) // axis = .vertical; alignment = .trailing

声明式间距

VerticalStack([ 
    SomeLabel(),

    UIStackViewSpace(12)

    SomeButton()

    UIStackViewSpace(15)

    UIImageView(image: ...)

    SomeView()
    ...
])

@ArrangedViewBuilder 函数构建器

HorizontalStack {
    AvatarImageView()    

    UIStackViewSpace(16)

    TopStack { 
        NameLabel()

        if user.isPremium { 
            PremiumMarkerView()
        }

        UIStackViewSpace(8)

        HStackView(tagsViews)
    }

    HorizontalCenterStack { 
        ShareButton()
        DisclosureButton()
    }.spacing(4).distribution(.fillEquality)
}

🧩 声明式约束构建器

您可以使用带有锚点约束的相同可链接样式来设置约束。

构建器的返回类型将是 AutoLayoutItem – 常量指令的简单存储。 要构建并激活它们,只需调用 activate() 函数。

let myLabel = UILabel()
    .numberOfLines(0) // -> UIView
    ...
    .heightAnchor(0) // -> AutoLayoutItem (same below)
    .topAnchor(16.from(anotherView.topAnchor))
    .leftAnchor(24)
    .rightAnchor(24.orLess.pririty(750))
    .verticalAnchor(backgroundView)
    .activate() // -> UIView (with applyed constraints)

🧮 约束构建器 DSL 规范

属性

最终公式

constant.from|to(_ target: NSLayoutAnchor).priority(NSLayoutPriority).orLess|orGreater

属性的顺序是**任意的**

15.from(secondView.topAnchor).orLess
20.orLess.to(secondView.bottomAnchor).priority(1000)
8.priority(.required).orGreater

额外的锚点

🧩 视图/构建器组合

private(set) weak var avatarView: UIImageView!

...

let profileView = UIView()
    .backgroundColor(.gray)
    .heightAnchor(100)
    .add({
        UIImageView()
            .assign(to: &avatarView)
            .contentMode(.scaleAspectFit)
            .sizeAnchor(40)
            .leftAnchor(16)
            .verticalAnchor(0)

        UILabel()
            .numberOfLines(2)
            .rightAnchor(0)
            .leftAnchor(8.from(avatarView.leftAnchor).priority(.required))
    })
    .activate()

或者使用便利的初始化器

let profileView = UIView {
    UIImageView()
        .assign(to: &avatarView)
        .contentMode(.scaleAspectFit)
        .sizeAnchor(40)
        .leftAnchor(16)
        .verticalAnchor(0)

    UILabel()
        .numberOfLines(2)
        .rightAnchor(0)
        .leftAnchor(8.from(avatarView.leftAnchor).priority(.required))
}
.backgroundColor(.gray)
.heightAnchor(100)
.activate()

ℹ️ 注意:如果您将约束构建器添加到 UIView (即,在调用 add(...) 之前未使用 anchor-chaining),则约束激活将在添加到 superview **之后立即发生**。 换句话说,activate() 函数的返回类型将是 Self (即 UIView)

let profileView = UIView()
    .backgroundColor(.gray)
    .add({
        UIImageView()
            .assign(to: &avatarView)
            .contentMode(.scaleAspectFit)
            .sizeAnchor(40)
            .leftAnchor(16)
            .verticalAnchor(0)
        UILabel()
            .numberOfLines(2)
            .rightAnchor(0)
            .leftAnchor(8.from(avatarView.leftAnchor).priority(.required))
    }) // -> UIView (with already added subviews)
    // and you can continue chainable-configuration (for example by specifying own anchors)
    .heightAnchor(100) // -> AutoLayoutItem
    .activate() // -> UIView

如何扩展链式功能?

第一种方法 – 编写返回 self 的类型扩展函数

extension MyCustomView {
    func myProperty(_ value: ValueType) -> Self {
        self.myProperty = value
        return self
    }
}

第二种方法 – 使用 SourceryChainable 模板应用于您的自定义视图 (查看教程)。

此框架与其他框架有何不同?

有**大量**的布局框架...
创建另一个框架的**主要目标**不是创建另一个框架,而是 *为快速编写布局代码制作工具
许多人都知道 SnapKit 是这些解决方案之一,但我决定制作一个**更具声明性和更简单的解决方案**。\

附注:在以前的版本中,该框架基于 DSL SnapKit,但现在拥有自己的、更类型安全的版本。

README 描述是您需要了解的全部内容,无需冗余文档。\

💉 您可以**轻松地将**该框架集成到项目中,并**将其与旧/现有布局代码结合起来**。

安装

CocoaPods

# Podfile
use_frameworks!

target 'YOUR_TARGET_NAME' do
    pod 'DeclarativeLayoutKit'
end

替换 YOUR_TARGET_NAME,然后在 Podfile 目录中键入

$ pod install

Swift Package Manager

创建一个 Package.swift 文件。

// swift-tools-version:5.0

import PackageDescription

let package = Package(
  name: "YOUR_PROJECT_NAME",
  dependencies: [
      .package(url: "https://github.com/Ernest0-Production/DeclarativeLayoutKit.git", from: "3.0.2")
  ],
  targets: [
      .target(name: "YOUR_TARGET_NAME", dependencies: ["DeclarativeLayoutKit"])
  ]
)

致谢

许可证

DeclarativeLayoutKit 在 MIT 许可证下发布。 有关详细信息,请参见LICENSE