如何在 Xcode 中为您自己和您的团队创建自定义模板节省开发时间

每当团队成长时,它就会决定采用特定的架构和特定的模式。它可以使用MVVM或VIPER。它可以使用 UIKit 或 SwiftUI。它可以使用面向协议的编程或协议见证方法。许多这些模式需要一些我们从不喜欢编写的样板。
任何 Xcode 开发人员都非常熟悉可以通过键入 ⌘+N 调用的“新建文件”屏幕。
在这里插入图片描述
此对话框为某些文件类型提供不同的模板。其中一些模板只是空文件。“Swfit 文件”出现时带有一些标题、一条import Foundation语句,仅此而已。其他人已经有几行代码可以进一步定制。
许多 iOS 开发人员不知道我们可以创建自己的模板。我们甚至可以创建包含不同文件的文件夹。在MVVM应用程序,就可以自动创建全View,ViewController,Model,和ViewModel样板。

模板在哪里?

模板是包含一些特定占位符、宏和配置的源文件,Xcode 在启动时读取。要创建我们自己的模板,我们需要知道 Xcode 将从哪个文件夹读取它们。
任何 Xcode 安装都带有自己的Templates文件夹,其中包含所有模板。对于默认安装,文件夹为:

/Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates

如果您open在这条路径上运行,您应该看到如下内容:

在这里插入图片描述
果您没有在默认路径中安装 Xcode,您始终可以通过导航到Xcode.app包然后按照/Contents/Developer/Library/Xcode/Templates路径来访问相同的目录。
比如我安装了不同版本的Xcode,Xcode 13.0Templates文件夹的路径如下:

/Applications/ Xcode-beta.app /Contents/Developer/Library/Xcode/Templates

什么是模板?

从这个位置,我们可以探索 Xcode 提供的所有标准模板。这是开始研究它们并学习我们能做什么的好地方。例如,我们可以深入/File Templates/Multiplatform/User Interface/路径并查看SwiftUI View模板是如何工作的。

在这里插入图片描述

在打开 Swift 文件之前,我们可以推断出一些规则:
模板的所有文件都分组到具有.xctemplate扩展名的文件夹中
Swift 文件有一个奇怪的名字:FILEBASENAME.swift.
有一个名为TemplateInfo.plist.
如果我们打开 Swift 文件,我们可以看到它的代码:

//___FILEHEADER___
import SwiftUI

struct ___FILEBASENAMEASIDENTIFIER___: View {
    var body: some View {
        Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
    }
}

struct ___FILEBASENAMEASIDENTIFIER____Previews: PreviewProvider {
    static var previews: some View {
        ___FILEBASENAMEASIDENTIFIER___()
    }
}

在这个文件中,有两种占位符。有以(三个下划线字符)开头和结尾的占位符/@和包含在和 中的占位符@/。
前者在创建文件时被 Xcode 替换,方法是从对话框中询问用户输入,或者用一些预配置的值替换它们,就像
FILEHEADER___占位符的情况一样。
后者以经典的自动完成外观出现在项目中:它们以浅灰色突出显示,我们可以使用TAB按钮浏览它们。
然后,我们可以打开TemplateInfo.plist文件:

在这里插入图片描述
此文件包含有关模板的一些元信息。它包含Kind文件名、文件类型(在AllowedTypes键中),我们甚至可以在Image/FileTypeIcon字段中提供自定义图标。
这里最重要的属性是MainTemplateFile:此属性将 映射TemplateInfo到实际模板。

在哪里可以创建自己的模板?

既然我们知道 Xcode 存储这些文件的位置,我们可能会想直接在这些文件夹中创建我们的自定义模板。然而,这还不是最聪明的事情:
如果我们使用不同版本的 Xcode,这些模板只能由我们保存它们的版本访问。
当我们用较新的版本更新该版本时,很有可能会删除文件夹并使用较新的文件夹重新创建,从而导致我们心爱的模板完全丢失。
为了解决这两个问题,我们可以利用系统中的另一个文件夹:

〜/图书馆/开发人员/Xcode

所有Xcode 版本都使用此文件夹来加载我们的首选项和自定义设置。它还包含许多其他文件夹: the DerivedData、 theProducts和 the UserData (其中包含我们在 Xcode 中自定义的所有片段和键绑定)。
注意:您可能还没有此处的Templates文件夹。您可以与第一个模板一起自行创建!

如何创建模板

要创建模板,我的建议是使用反向方法:
弄清楚什么可以用作模板。一般来说,它是我们反复使用的东西,需要一些样板。
编写它的非模板版本。使用真实的类、结构和变量名称创建文件。在操场或真实项目中创建它,以便您可以测试它是否正常工作。
将其转换为模板。这应该是用适当的宏替换一些名称的问题。
将其移动到~/Library/Developer/Xcode/Templates自定义子文件夹中的文件夹。
TemplateInfo.plist在同一个子文件夹中创建。
测试一切正常。
弄清楚要模板化的内容
我们已经列举了几种可能性:MVVM / VIPER件,证人与live和unimplemented骨骼等。
为了这篇文章,我想为一个View利用 SwiftUI 预览的 UIKit 创建一个模板。
编写非模板版本
我通常使用 MVVM,其中ViewModel(VM)与View. 所以第一步是使用 UIKit 创建一个简单的 View+VM 文件

import UIKit

// MARK: - ViewModel
public struct ViewModel {
  let text: String

  public init(text: String) {
    self.text = text
  }
}

// MARK: - View
public class View: UIView {

  /// MVVM Binding: it triggers the update whenever the viewModel changes
  public var viewModel: ViewModel? {
    didSet {
      self.update(old: oldValue)
    }
  }

  // MARK: Properties
  let label = UILabel()

  // MARK: Initializers
  override init(frame: CGRect) {
    super.init(frame: frame)
    self.setup()
    self.style()
  }

  required init?(coder: NSCoder) {
    fatalError("Not implemented")
  }

  // MARK: Setup
  func setup() {
    self.addSubview(self.label)
  }

  // MARK: Style
  func style() {
    self.backgroundColor = .white
    self.label.textColor = .black
  }

  // MARK: Update
  func update(old: ViewModel?) {
    self.label.text = self.viewModel?.text ?? ""
  }

  // MARK: Layout
  public override func layoutSubviews() {
    super.layoutSubviews()

    self.label.frame = .zero
    self.label.sizeToFit()
    self.label.center = self.center
  }
}

我们创建了一个ViewModel包含String属性和一些默认代码来呈现它的。我们可以立即看到有很多样板。每个视图都具有相同的viewModel与它的属性观察,相同的属性setup,style和update方法签名,和相同的初始化。
注意:其中一些东西可以在适当的基类中分解出来,而不是从UIView. 如果可能,将它们分解并在更具体的父级中将它们子类化是更好的方法。
如果我们在视图控制器中测试这个视图,我们会得到以下结果

第二步是实际添加 SwiftUI 预览功能。让我们在文件底部添加此代码。为简洁起见,这是前一个文件末尾的扩展名。

// other part of the View
}

#if DEBUG

import SwiftUI

struct PreviewedView: View, UIViewRepresentable {
  typealias UIViewType = View

  var viewModel: ViewModel

  func makeUIView(context: Context) -> View {
    let view = View()
    view.viewModel = self.viewModel
    return view
  }

  func updateUIView(_ uiView: UIViewType, context: Context) {
    uiView.viewModel = self.viewModel
  }
}

struct PreviewedView_Previews: PreviewProvider {
  static var previews: some View {
    let model = ViewModel(text: "Hello hello")
    return PreviewedView(viewModel: model)
  }
}
#endif

我不会详细说明这是如何工作的。几个月前我已经写了一篇关于它的文章,如果你有兴趣,我建议你看看它。
简而言之,我们需要创建一个可以映射到我们的 UIKit 组件中的 SwiftUI 视图。这可以使用UIViewRepresentable协议来完成。然后,我们可以利用 SwiftUIPreviewProvider创建预览。完成后,Xcode 中应弹出预览面板

将其转换为模板

在修改代码之前,有一些细节值得讨论。正如我们预期的那样,Xcode 的模板系统通过利用占位符来工作。让我们看看最常见的:
FILEHEADER 是 Xcode 在创建文件时放置在每个 Swift 文件中的头文件的占位符。
FILEBASENAME是一个占位符,它会在 Xcode 的“新建文件”对话框中触发一个额外的对话框。在我们选择使用自定义模板创建新文件后,Xcode 会要求我们选择文件名。此名称替换此标识符。
FILEBASENAMEASIDENTIFIER是 Xcode 用来获取FILEBASENAME值并将其用作标识符的占位符。然后我们可以给它添加一个后缀来表征不同的实体。例如,FILEBASENAMEASIDENTIFIERViewModel可用于创建与文件同名加上ViewModel后缀的结构体。
通过使用这些占位符并删除当前特定于视图的部分,模板的最终代码如下所示:

//___FILEHEADER___
import UIKit

// MARK: - ViewModel
public struct ___FILEBASENAMEASIDENTIFIER___ViewModel {
}

// MARK: - View
public class ___FILEBASENAMEASIDENTIFIER___View: UIView {

  /// MVVM Binding: it triggers the update whenever the viewModel changes
  public var viewModel: ___FILEBASENAMEASIDENTIFIER___ViewModel? {
    didSet {
      self.update(old: oldValue)
    }
  }

  // MARK: Properties
  // MARK: Initializers
  override init(frame: CGRect) {
    super.init(frame: frame)
    self.setup()
    self.style()
  }

  required init?(coder: NSCoder) {
    fatalError("Not implemented")
  }

  // MARK: Setup
  func setup() {
  }

  // MARK: Style
  func style() {
  }

  // MARK: Update
  func update(old: ___FILEBASENAMEASIDENTIFIER___ViewModel?) {
  }

  // MARK: Layout
  public override func layoutSubviews() {
    super.layoutSubviews()
  }
}

#if DEBUG

import SwiftUI

struct Previewed___FILEBASENAMEASIDENTIFIER___View: View, UIViewRepresentable {
  typealias UIViewType = ___FILEBASENAMEASIDENTIFIER___View

  var viewModel: ___FILEBASENAMEASIDENTIFIER___ViewModel

  func makeUIView(context: Context) -> ___FILEBASENAMEASIDENTIFIER___View {
    let view = ___FILEBASENAMEASIDENTIFIER___View()
    view.viewModel = self.viewModel
    return view
  }

  func updateUIView(_ uiView: UIViewType, context: Context) {
    uiView.viewModel = self.viewModel
  }
}

struct Previewed___FILEBASENAMEASIDENTIFIER___View_Previews: PreviewProvider {
  static var previews: some SwiftUI.View {
    let model = ___FILEBASENAMEASIDENTIFIER___ViewModel()
    return
      Previewed___FILEBASENAMEASIDENTIFIER___View(viewModel: model)

  }
}
#endif

将模板移动到自定义模板文件夹

此步骤使文件可用于 Xcode。让我们运行命令导航到正确的文件夹:

cd ~/库/开发人员/Xcode/

如果我们没有Templates文件夹,我们需要在其中创建一个File Templates文件夹。之后,让我们导航到它们:

mkdir -p "模板/文件模板" 
cd "模板/文件模板"

现在,我们需要创建一个文件夹来包含我们所有的自定义模板。让我们称之为Custom Templates。在 Xcode 对话框中,这是一个可以包含许多模板的部分。然后我们PreviewedUIKit.xctemplate在其中创建文件夹并导航到该xctemplate文件夹中。

mkdir -p "Custom Templates/PreviewedUIKit.xctemplate" 
cd "Custom Templates/PreviewedUIKit.xctemplate" 
touch ___FILEBASENAME___.swift 
touch TemplateInfo.plist

最后两行创建一个空Swift文件和一个空的TemplateInfo.plist. 让我们打开FILEBASENAME.swift上面创建的模板并将其粘贴到其中。
创建 TemplateInfo.plist
最后一步是创建TemplateInfo.plist文件。
我的建议是首先复制和粘贴TemplateInfo.plist您可以在默认 Xcode 模板中找到的文件之一,然后根据需要对其进行修改。就我而言,我的最后一个方面plist如下。
在这里插入图片描述

这里有趣的属性是:
DefaultCompletionName — Xcode 使用它来建议文件的名称。
Platforms — 通过将其留空,我们正在创建一个多平台模板。
Image— 通过FileTypeIcon使用swift值指定键,Xcode 为我们的模板使用默认的 Swift 图标。
注意:还有许多其他属性可用于进一步自定义我们的模板。例如,我们可以在 中定义一个可以在模板中使用占位符访问的Options键。不幸的是,我还没有找到任何关于 Xcode 模板的官方详细文档。TemplateInfo.plist _VARIABLEoptionName

测试我们的模板

最后但并非最不重要的是,我们需要测试我们的模板。作为第一步,我们需要关闭并重新打开 Xcode。IDE 需要刷新可用模板以允许我们使用它们。
然后,让我们按 ⌘+N 打开“新建文件”对话框并向下滚动到列表末尾。就是这样——我们新的闪亮模板!

让我们选择它并为视图命名。我选择了AppSetup。这是 Xcode 使用我们的模板自动生成的文件:


//
//  AppSetup.swift
//  SPMAppUIKit
//
//  Created by Riccardo Cipolleschi on 09/07/21.
//
import UIKit

// MARK: - ViewModel
public struct AppSetupViewModel {
}

// MARK: - View
public class AppSetupView: UIView {

  /// MVVM Binding: it triggers the update whenever the viewModel changes
  public var viewModel: AppSetupViewModel? {
    didSet {
      self.update(old: oldValue)
    }
  }

  // MARK: Properties
  // MARK: Initializers
  override init(frame: CGRect) {
    super.init(frame: frame)
    self.setup()
    self.style()
  }

  required init?(coder: NSCoder) {
    fatalError("Not implemented")
  }

  // MARK: Setup
  func setup() {
  }

  // MARK: Style
  func style() {
    self.backgroundColor = .systemTeal
  }

  // MARK: Update
  func update(old: AppSetupViewModel?) {
  }

  // MARK: Layout
  public override func layoutSubviews() {
    super.layoutSubviews()
  }
}

#if DEBUG

import SwiftUI

struct PreviewedAppSetupView: View, UIViewRepresentable {
  typealias UIViewType = AppSetupView

  var viewModel: AppSetupViewModel

  func makeUIView(context: Context) -> AppSetupView {
    let view = AppSetupView()
    view.viewModel = self.viewModel
    return view
  }

  func updateUIView(_ uiView: UIViewType, context: Context) {
    uiView.viewModel = self.viewModel
  }
}

struct PreviewedAppSetupView_Previews: PreviewProvider {
  static var previews: some View {
    let model = AppSetupViewModel()
    return PreviewedAppSetupView(viewModel: model)
  }
}
#endif

结论

今天,我们学习了如何在 Xcode 中创建模板。我们看到了它们是如何工作的以及它们需要哪些文件才能正常工作。
我们了解了用于创建基本模板的占位符和配置。
我们没有涉及如何创建包含多个文件的文件夹或如何使用更高级的机制,如变量。不幸的是,我找不到任何关于如何在 Xcode 中创建模板的官方文档。目前,最好的学习方法是花一些时间探索默认的 Xcode 模板并在 GitHub 上抓取一些开源存储库。只需搜索占位符之一,它们就会弹出。


精品教程推荐


加入我们一起学习SwiftUI

QQ:3365059189
SwiftUI技术交流QQ群:518696470
教程网站:www.openswiftui.com

发表回复