最近在写学习英语小助手时发现随着功能的增加,代码中的命名空间有了不少问题。另外,接下来会有其它的大朋友或小朋友加入一起来写一些功能了。这就不可避免的出现了两个想法,一是能否把不相干的功能分成不同的项目(或模块),二是能否把这些项目组织起来方便共同开发。最先开始,我想到了使用Swift Package Mangment,但是如果只是为了模块化项目,我们完全不必修改->提交->update这么痛苦。在新的Xcode 13里为使用Swift Package Mangment来模块化项目做了不少工作。这里记录一下,方便将来回查。
新建workspace
我们使用Xcode的workspace来整理所有的SwiftPM和相关的App,所以需要先创建一个workspace。点击Xcode菜单中的File->New->workspace...
我们给workspace文件名取名为MyTestApp.xcworkspace,将它放置在存储所有项目的根目录中(我建立了一个目录名叫MyTestApp)
这样我们就拥有了一个workspace,对于不同的SwiftPM其实可以建立多个完全不同的workspace,用来帮助开发SwiftPM和做这个SwiftPM的DemoApp。现在Xcode看起来是一个空空如野的样子:
新建MyTestApp项目
这个项目是我们用于最终发部的项目,点击Xcode的菜单File->New->Project...,在里面选择App
在写好你的项目名称为MyTestApp后,我们把它存放的目录选择到与workspace相同的目录里,因为这个project会自动创建一个MyTestApp的目录的,这时有一个非常重要的选项,在下方的Add to里你需要将它选择为我们刚创建的workspace:
这样我们就得到了一个workspace,并且包括了一个App Project。
新建MyPackage
接下来我们新建一个SwiftPM,点击Xcode菜单中的File->New->Package...,在这个对话框中为我们的Package设置为MyPackage:
这里也需要注意,要选择把这个新建的Package设置为Add to到我们刚建的workespace里,同时你会发现可以让你选择把它放在哪个Group里,我个人比较喜欢放在workespace的根上。
到现在我们已经在workspace里设置好了一个App和一个Package。
将MyPackage加入到App的Embed列表中去
在Xcode的navigator列表中选中MyTestApp,然后在TARGETS中选择MyTestApp,在General分类里找到Frameworks, Libraries, and Embedded Content,然后点下面的+号后,在里边找到MyPackage的Library点Add:
这样就将MyPackage做为一个Library可以在MyTestApp里被使用了。加好后如下图所示:
使用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()
}
}
我们最后来看看效果
设置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的条目了:
接下来,在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
}
}
}
}
再看来看效果
这样让我们通过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"]),
]
)