美文网首页
08 | 设计组件:DesignKit 组件桥接设计与开发规范

08 | 设计组件:DesignKit 组件桥接设计与开发规范

作者: 清风烈酒2157 | 来源:发表于2021-04-19 09:54 被阅读0次

[toc]

前言

本文来自拉勾网课程整理

在上一模块“配置与规范”中,主要介绍了如何统一项目的配置,以及如何制定统一开发和设计规范。

接下来我们将进入基础组件设计模块,我会为你介绍一些在iOS开发过程中,工程化实践需要用的组件,比如设计组件、路由组件。除此之外,我还会聊聊在开发中如何支持多语言、动态字体和深色模式等辅助功能,让你的App既有国际范,获取更多用户,还能提升用户体验,获得更多好评。

这一讲,我们就先来聊聊公共组件库,以及如何封装基础设计组件。

封装公共功能组件库

随着产品不断发展,我们会发现,越来越多的公共功能可以封装成组件库,从而被各个模块甚至多个App共同使用,比如字体、调色板、间距头像可以封装成UI设计组件库,登录会话和权限管理可以封装成登录与鉴权组件库。

通过利用这些公共功能组件库,不仅能节省大量开发时间,不需要我们再为每个模块重复实现类似的功能;还能减少编译时间,因为如果没有独立的组件库,一点代码的改动都会导致整个 App 重新编译与链接。

那么,怎样才能创建和使用公共功能组件库呢?下面我们以一个设计组件库 DesignKit 为例子介绍下具体怎么做。

创建内部公共功能组件库

公共功能组件库根据使用范围可以分为三大类:内部库、私有库和开源库

  • 内部库是指该库和主项目共享一个Repo,它可以共享到主项目的所有模块中。
  • 私有库是指该库使用独立的私有 Repo,它可以共享到公司多个App中。
  • 开源库是指该库发布到GitHub等开源社区提供给其他开发者使用。

这三类库的创建和使用方式都是一致的。在实际操作中,我们一般先创建内部库,如果今后有必要,可以再升级为私有库乃至开源库。下面咱们一起看看怎样创建内部库。

为了方便管理各个内部公共功能组件库,首先我们新建一个叫作Frameworks 的文件夹来保存所有的内部库。这个文件夹和主项目文件夹(在我们例子中是Moments)以及 Workplace 文档(Moments.xcworkspace)平衡。例如下面的文件结构:

Frameworks          Moments             Pods            Moments.xcworkspace

然后我们通过CocoaPods创建和管理这个内部库。

怎么做呢?有两种办法可以完成这项工作,一种是使用pod lib create [pod name]命令。比如在这个案例当中,我们可以在 Frameworks 文件夹下执行bundle exec pod lib create DesignKit命令,然后输入邮箱、语言和平台等信息,让 CocoaPods创建一个 DesignKit.podspec 以及例子项目等一堆文件。具体如下:

DesignKit         Example           README.md
DesignKit.podspec LICENSE           _Pods.xcodeproj

DesignKit.podspecDesignKit 库的 Pod 描述文件,用于描述该 Pod 库的一个特定版本信息。它存放在 CocoaPods 的中心 Repo 供使用者查找使用

随着这个Pod库的迭代,CocoaPods的中心 Repo会为每个特定的 Pod 版本存放一个对应的 podspec 文件。每个 podspec 文件都包括 Pod 对应RepoURL、源码存放的位置、所支持的系统平台及其系统最低版本号、Swift 语言版本,以及 Pod的名字、版本号和描述等信息。

DesignKit 组件库的podspec 文件你可以在仓库中找到。下面是该 podspec 文件的一些


  s.name             = 'DesignKit'
  s.version          = '1.0.0'

  s.ios.deployment_target = '14.0'
  s.swift_versions = '5.3'
  s.source_files = 'src/**/*'
  s.resources = 'assets/**/*'

name是该组件的名字,version是组件的版本号,当我们更新组件的时候同时需要使用 Semantic Versioning(语义化版本号)更新该版本号。

ios.deployment_target为该库所支持的平台和所支持平台的最低版本号。swift_versions是支持 Swift 语言的版本号。source_files是该库的源代码所在的文件夹,在我们例子中是 src。resources是该库资源文件所在的文件夹。

另外一种是手工创建 DesignKit.podspec 文件。我偏向于这一种,因为手工创建出来的项目更简练。

比如在这里,我们只需要在 Frameworks 新建一个叫作 DesignKit 的文件夹,然后在它下面建立 srcassets 这两个文件夹,以及 LICENSEDesignKit.podspec 这两个文件即可。

如下所示:

DesignKit.podspec LICENSE           assets            src

以后所有源代码文件都存放在 src 文件夹下面,而图片、XibStoryboard 等资源文件存放在 assets 文件夹下。

LICENSE 是许可证文件,如果是开源库,我们必须严格选择一个许可证,这样才能方便其他开发者使用我们的库。

检测内部公共功能组件库

为了保证组件库的使用者能顺利安装和使用我们的库,当我们配置好 DesignKit.podspec 文件后,需要执行bundle exec pod spec lint命令来检测该podspec文件是否正确。如果我们维护的是一个开源库,这一步尤为重要。因为它会影响到使用者的第一印象,因此我们在发布该Pod之前需要把每个错误或者警告都修复好。

不过需要注意的是, CocoaPods 对内部库的检测存在一个 Bug, 会显示下面的警告:

Missing primary key forsourceattribute

由于我们创建的是内部库,所以可以忽略这个警告,只要没有其他错误信息就可以了。

使用内部公共功能组件库

使用内部公共功能组件库非常简单,只要在主项目的 Podfile 里面使用:path来指定该内部库的路径即可。

pod 'DesignKit', :path => './Frameworks/DesignKit', :inhibit_warnings => false

当执行bundle exec pod install命令以后,CocoaPods 会在 Pods 项目下建立一个Development Pods文件夹来存放所有内部库的相关文件。

609b1c85be63c6334567e3fee94ac917

有了 CocoaPods,我们新建、管理和使用公共组件库就会变得非常简单。下面我们介绍下如何开发设计组件 DesignKit

DesignKit 设计组件

DesignKit 是一个设计组件,用于封装与UI相关的公共组件。为了方便维护,每次新增一个组件,我们最好都建立一个独立的文件夹,例如把 Spacing.swift 放在新建的 Spacing 文件夹中。

8a1670cb6ca5b46473d1aad2b4094bd5

下面以几乎每个App都会使用到的三个组件:间距(Spacing)、头像(Avatar)和点赞按钮(Favorite Button)为例子,介绍下如何封装基础设计组件。

间距

为了呈现信息分组并体现信息的主次关系,所有App的所有页面都会使用到间距来添加留白效果。

间距看起来这么简单,为什么我们还需要为其独立封装为一个公共组件呢?主要原因有这么几条。

  • 可以为整个App提供一致的体验,因为我们统一定义了所有间距,各个功能模块的UI呈现都保持一致。
  • 可以减低设计师和开发者的沟通成本,不会再为某些像素值的多与少而争论不休。设计师只使用预先定义的间距,而开发者也只使用在代码中定义好的间距就行了。
  • 可以减低设计师的工作量,很多UI界面可以只提供一个设计稿来同时支持 iOS、Android 以及移动 Web。因为设计师只提供预先定义的间距名,而不是 hardcoded (硬编码)的像素值。不同设备上像素值有可能不一样,但间距名却能保持一致。
  • 在支持响应式设计的时候,这些间距定义可以根据设备的宽度而自动调整。这远比硬编码的像素值灵活很多,例如在iPhonetwoExtraSmall4 points,而在 iPad 中是 6 points

别看间距公共组件有那么多优点,但实现起来并不难,一个struct就搞定了,简直是一本万利的投入。

public struct Spacing {
    public static let twoExtraSmall: CGFloat = 4
    public static let extraSmall: CGFloat = 8
    public static let small: CGFloat = 12
    public static let medium: CGFloat = 18
    public static let large: CGFloat = 24
    public static let extraLarge: CGFloat = 32
    public static let twoExtraLarge: CGFloat = 40
    public static let threeExtraLarge: CGFloat = 48
}


有了上述的定义以后,使用这些间距变得很简单。请看:

import DesignKit

private let likesStakeView: UIStackView = configure(.init()) {
    $0.spacing = Spacing.twoExtraSmall
    $0.directionalLayoutMargins = NSDirectionalEdgeInsets(top: Spacing.twoExtraSmall, leading: Spacing.twoExtraSmall, bottom: Spacing.twoExtraSmall, trailing: Spacing.twoExtraSmall)
}

我们可以先 import (引入) DesignKit 库,然后通过Spacing结构体直接访问预定义的间距,例如Spacing.twoExtraSmall

头像组件

iOS开发者都知道,头像组件应用广泛,例如在房产App中显示中介的头像,在我们例子 Moments App中显示自己和好友头像,在短视频App 中显示视频博主头像等。

也许你会问,头像那么简单,为什么需要独立封装为一个组件?原因主要是方便以后改变其UI的呈现方式,例如从圆角方形改成圆形,添加边界线(border),添加阴影效果(shadow)等。有了独立的组件以后,我们只需要修改一个地方就能把这个App的所有头像一次性地修改呈现效果。

下面是头像组件的实现方式:

public extension UIImageView {
    func asAvatar(cornerRadius: CGFloat = 4) {
        clipsToBounds = true
        layer.cornerRadius = cornerRadius
    }
}

我们为UIKit 所提供的UIImageView实现了一个扩展方法asAvatar(cornerRadius:),该方法接收cornerRadius作为参数来配置圆角的角度,默认值是4

使用也是非常简单,只有创建一个UIImageView的实例,然后调用asAvatar(cornerRadius:)方法即可。

    private let userAvatarImageView: UIImageView = configure(.init()) {
        $0.asAvatar(cornerRadius: 4)
    }


点赞按钮

可以说,每个具有社交属性的App都会用到点赞功能,所以在开发当中,点赞按钮也是必不可少的功能组件。

那么,点赞按钮该如何封装呢?和人像组件十分类似,我们可以通过扩展UIButton来实现。示例代码如下:

public extension UIButton {
    func asStarFavoriteButton(pointSize: CGFloat = 18, weight: UIImage.SymbolWeight = .semibold, scale: UIImage.SymbolScale = .default, fillColor: UIColor = UIColor(hex: 0xf1c40f)) {
        let symbolConfiguration = UIImage.SymbolConfiguration(pointSize: pointSize, weight: weight, scale: scale)
        let starImage = UIImage(systemName: "star", withConfiguration: symbolConfiguration)
        setImage(starImage, for: .normal)
        let starFillImage = UIImage(systemName: "star.fill", withConfiguration: symbolConfiguration)
        setImage(starFillImage, for: .selected)
        tintColor = fillColor
        addTarget(self, action: #selector(touchUpInside), for: .touchUpInside)
    }
}
private extension UIButton {
    @objc
    private func touchUpInside(sender: UIButton) {
        isSelected = !isSelected
    }
}


其核心逻辑把当前UIButton对象的普通 (.normal) 状态和选中 (.selected) 状态设置不同的图标。比如在这里我就把星星按钮的普通状态设置成了名叫 “Star” 的图标,并把它的选中状态设置成了名叫 “tar.fill"” 的图标。

注意,这些图标来自苹果公司的SF Symbols 不需要额外安装,iOS 14 系统本身就自带了。而且它们的使用也非常灵活,支持字号、字重、填充色等配置。

使用点赞按钮组件也非常简单,只需要建立一个UIButton的实例,然后调用asStarFavoriteButton方法就可以了。

    private let favoriteButton: UIButton = configure(.init()) {
        $0.asStarFavoriteButton()
    }


点赞按钮的运行效果,也可以在内部菜单查看。

以上我们以间距、头像、点赞按钮为例介绍了如何使用DesignKit封装与UI相关的公共组件。以我多年的开发经验来说,在封装UI组件的时候,可以遵循下面几个原则。

  • 尽量使用扩展方法而不是子类来扩展组件,这样做可以使其他开发者在使用这些组件时,仅需要调用扩展方法,而不必使用特定的类。
  • 尽量使用代码而不要使用 Xib 或者 Storyboard,因为有些App完全不使用Interface Builder
  • 如果可以,要为组件加上@IBDesignable@IBInspectable支持,这样能使得开发者在使用 Interface Builder的时候预览我们的组件。
  • 尽量只使用UIkit而不要依赖任何第三方库,否则我们可能会引入一个不可控的依赖库。

总结

前面我介绍了如何封装公共功能组件库,以及以怎样封装基础设计组件,希望对你有所帮助。合理使用功能组件可以让你的开发事半功倍。

60cf7c8bc849632285ac557f8916e914

不过,在封装组件的时候,还需要提醒你注意这么几点。

首先,为了减低组件之间的耦合性,提高组件的健壮性,组件的设计需要符合单一功能原则 。也就是说,一个组件只做一件事情,一个组件库只做一类相关的事情。每个组件库都要相对独立且功能单一。

比如,我们可以分别封装网络库、UI 库、蓝牙处理库等底层库,但不能把所有库合并在一个单独的库里面,这样可以方便上层应用按需使用这些依赖库。例如,广告 SDK 可以依赖于网络库、UI库,但并不依赖蓝牙处理库。这样做一方面可以减少循环依赖的可能性,另一方面可以加快编译和链接的速度,方便使用。

其次,每次发布新增和更新组件的时候,都需要严格按照 Semantic Versioning 来更新版本号,这样有效防止因为版本的问题而引入Bug

最后,组件的开发并不是一蹴而就,很多时候可以根据业务需求把公共模块一点点地移入公共组件库中,一步步地完善组件库的功能。不要为了开发组件而开发组件,很多时候当我们充分理解了使用者的需求后,才能为组件定义完善的接口和完整的功能。

源码地址:

DesignKit 源代码:https://github.com/lagoueduCol/iOS-linyongjian/tree/main/Frameworks/DesignKit

相关文章

网友评论

      本文标题:08 | 设计组件:DesignKit 组件桥接设计与开发规范

      本文链接:https://www.haomeiwen.com/subject/waxilltx.html