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

最近在写学习英语小助手时发现随着功能的增加，代码中的命名空间有了不少问题。另外，接下来会有其它的大朋友或小朋友加入一起来写一些功能了。这就不可避免的出现了两个想法，一是能否把不相干的功能分成不同的项目（或模块），二是能否把这些项目组织起来方便共同开发。最先开始，我想到了使用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](https://cdn.hashnode.com/res/hashnode/image/upload/v1645410326388/HE81vqbTN.png)

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

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1645410470680/w-ZRjxmXc.png)

### 新建MyTestApp项目

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

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1645410628941/6qMOhOCGN.png)

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

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1645410834709/aDWd0D9qH.png)

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

### 新建MyPackage

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

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1645412111533/L4nGHVBaj.png)

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

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

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1645412326137/CgyadQZNg.png)

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

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

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1645412659716/Ptsousknb.png)

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

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1645412817799/oszadERDI.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](https://cdn.hashnode.com/res/hashnode/image/upload/v1645413664447/q-37jwVhi.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](https://cdn.hashnode.com/res/hashnode/image/upload/v1645416472056/pNTSzO63f.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](https://cdn.hashnode.com/res/hashnode/image/upload/v1645416657540/XZNG67hE4.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"]),
    ]
)
```


