使用Swift Package来模块化SwiftUI项目小纸条

使用Swift Package来模块化SwiftUI项目小纸条

·

2 min read

最近在写学习英语小助手时发现随着功能的增加,代码中的命名空间有了不少问题。另外,接下来会有其它的大朋友或小朋友加入一起来写一些功能了。这就不可避免的出现了两个想法,一是能否把不相干的功能分成不同的项目(或模块),二是能否把这些项目组织起来方便共同开发。最先开始,我想到了使用Swift Package Mangment,但是如果只是为了模块化项目,我们完全不必修改->提交->update这么痛苦。在新的Xcode 13里为使用Swift Package Mangment来模块化项目做了不少工作。这里记录一下,方便将来回查。

新建workspace

我们使用Xcode的workspace来整理所有的SwiftPM和相关的App,所以需要先创建一个workspace。点击Xcode菜单中的File->New->workspace...

我们给workspace文件名取名为MyTestApp.xcworkspace,将它放置在存储所有项目的根目录中(我建立了一个目录名叫MyTestApp)

image.png

这样我们就拥有了一个workspace,对于不同的SwiftPM其实可以建立多个完全不同的workspace,用来帮助开发SwiftPM和做这个SwiftPM的DemoApp。现在Xcode看起来是一个空空如野的样子:

image.png

新建MyTestApp项目

这个项目是我们用于最终发部的项目,点击Xcode的菜单File->New->Project...,在里面选择App

image.png

在写好你的项目名称为MyTestApp后,我们把它存放的目录选择到与workspace相同的目录里,因为这个project会自动创建一个MyTestApp的目录的,这时有一个非常重要的选项,在下方的Add to里你需要将它选择为我们刚创建的workspace:

image.png

这样我们就得到了一个workspace,并且包括了一个App Project。

新建MyPackage

接下来我们新建一个SwiftPM,点击Xcode菜单中的File->New->Package...,在这个对话框中为我们的Package设置为MyPackage:

image.png

这里也需要注意,要选择把这个新建的Package设置为Add to到我们刚建的workespace里,同时你会发现可以让你选择把它放在哪个Group里,我个人比较喜欢放在workespace的根上。

到现在我们已经在workspace里设置好了一个App和一个Package。

image.png

将MyPackage加入到App的Embed列表中去

在Xcode的navigator列表中选中MyTestApp,然后在TARGETS中选择MyTestApp,在General分类里找到Frameworks, Libraries, and Embedded Content,然后点下面的+号后,在里边找到MyPackage的Library点Add:

image.png

这样就将MyPackage做为一个Library可以在MyTestApp里被使用了。加好后如下图所示:

image.png

使用MyPackage中的变量与函数

我们先来改变下MyPackage.swift中的代码:

public struct MyPackage {
    public private(set) var text = "Hello, World!"

    public init() {
    }

    public func getMessag() -> String{
        return "This is a MyPackage's message."
    }
}

注意,这里我们将所有需要被Package外使用的资源都加入了public的声明,只有加了public的声明,才可以在后面的MyTestApp中所使用到。

再接下来,我们来修改一下MyTestApp中ContentView中的内容:

import SwiftUI
import MyPackage // 为了使用MyPackage中的内容需要先import

struct ContentView: View {
    // 将MyPackage struct实例化为一个State变量
    @State var vm = MyPackage()
    // 设置一个State用来存储message
    @State var message = ""

    var body: some View {
        VStack{
            Text("message:\(message)")
            Text(vm.text)
                .padding()
                .onTapGesture {
                    message = vm.getMessag()
                }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

我们最后来看看效果

demo.gif

设置SwiftPM的支持平台及版本

在Package.swift中可以声明你所支持的平台及版本,就像这样

import PackageDescription

let package = Package(
    name: "MyPackage",
    // 加入所支持的平台及版本
    platforms: [.iOS(.v15)],
    products: [
        .library(
            name: "MyPackage",
            targets: ["MyPackage"]),
    ],
    dependencies: [],
    targets: [
        .target(
            name: "MyPackage",
            dependencies: []),
        .testTarget(
            name: "MyPackageTests",
            dependencies: ["MyPackage"]),
    ]
)

利用SwiftPM的依赖来管理依赖

我们可以利用SwiftPM的包管理来帮我们管理依赖项目,比如在Package.swift中,加入我写的帮助翻译的Package:

import PackageDescription

let package = Package(
    name: "MyPackage",
    platforms: [.iOS(.v15)],
    products: [
        .library(
            name: "MyPackage",
            targets: ["MyPackage"]),
    ],
    dependencies: [
        // 加入package的dependencies条目
        .package(url: "https://github.com/HDCodePractice/TranslateController.git", from: "0.0.3")
    ],
    targets: [
        .target(
            name: "MyPackage",
            // 在target中加入相应的条目
            dependencies: [.product(name: "TranslateController", package: "TranslateController")]),
        .testTarget(
            name: "MyPackageTests",
            dependencies: ["MyPackage"]),
    ]
)

这时就可以看到在Xcode的navigator中看到TranslateController的条目了:

image.png

接下来,在ContentView中加入翻译的能力

import SwiftUI
import MyPackage
import TranslateController

struct ContentView: View {
    @State var vm = MyPackage()
    @State var message = ""
    @State var show_translate = false

    var body: some View {
        VStack{
            TranslateController(text: $message, showing: $show_translate)
                .frame(width: 0, height: 0)
            Text("message:\(message)")
            Text(vm.text)
                .padding()
                .onTapGesture {
                    message = vm.getMessag()
                    show_translate = true
                }
        }
    }
}

再看来看效果

demo.gif

这样让我们通过SwiftPM来管理模块就非常容易了。甚至我们可以用这样的方式来管理本地的更多模块:

import PackageDescription

let package = Package(
    name: "MyPackage",
    platforms: [.iOS(.v15)],
    products: [
        .library(
            name: "MyPackage",
            targets: ["MyPackage"]),
    ],
    dependencies: [
        .package(url: "https://github.com/HDCodePractice/TranslateController.git", from: "0.0.3"),
        // 加入本地package的dependencies条目
        .package(path: "../MyOtherPackage")
    ],
    targets: [
        .target(
            name: "MyPackage",
            // 在target中加入相应的条目
            dependencies: [.product(name: "TranslateController", package: "TranslateController"),
                                       .product(name: "MyOtherPackage",package: "MyOtherPackage" )]),
        .testTarget(
            name: "MyPackageTests",
            dependencies: ["MyPackage"]),
    ]
)

Did you find this article valuable?

Support 老房东 by becoming a sponsor. Any amount is appreciated!