Skip to content

Commit

Permalink
add layout
Browse files Browse the repository at this point in the history
  • Loading branch information
ming1016 committed May 15, 2024
1 parent c5771e4 commit 04a2aa0
Show file tree
Hide file tree
Showing 14 changed files with 484 additions and 6 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 戴铭的开发小册子 6.0
[![Available on the App Store](https://ming1016.github.io/qdimg/badge-download-on-the-mac-app-store.svg)](https://apps.apple.com/cn/app/id1609702529)

Swift开发的手册,是个 macOS 程序,已上线macOS应用商店,[点击安装](https://apps.apple.com/cn/app/%E6%88%B4%E9%93%AD%E7%9A%84%E5%BC%80%E5%8F%91%E5%B0%8F%E5%86%8C%E5%AD%90/id1609702529?mt=12)或直接在商店搜索戴铭,方便更新程序。小册子文字版 《[戴铭的 Swift 小册子](https://ming1016.github.io/2021/11/23/daiming-swift-pamphlet/)
Swift开发的手册,是个 macOS 程序,已上线macOS应用商店,[点击安装](https://apps.apple.com/cn/app/%E6%88%B4%E9%93%AD%E7%9A%84%E5%BC%80%E5%8F%91%E5%B0%8F%E5%86%8C%E5%AD%90/id1609702529?mt=12)或直接在商店搜索“戴铭”关键字,方便更新程序。小册子文字版 《[戴铭的 Swift 小册子](https://ming1016.github.io/2021/11/23/daiming-swift-pamphlet/)

使用 SwiftData、Observable、NavigationSplitView 进行了重构,现在可自己添加管理资料,和知识点做关联。

Expand Down
36 changes: 36 additions & 0 deletions SwiftPamphletApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,13 @@
08D4EBD32BF437510031EDC5 /* Navigation(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D4EBD22BF437510031EDC5 /* Navigation(ap).md */; };
08D4EBD52BF4379E0031EDC5 /* NavigationStack(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D4EBD42BF4379E0031EDC5 /* NavigationStack(ap).md */; };
08D4EBD72BF43C540031EDC5 /* NavigationPath(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D4EBD62BF43C540031EDC5 /* NavigationPath(ap).md */; };
08D4EBD92BF44C170031EDC5 /* NavigationSplitView(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D4EBD82BF44C170031EDC5 /* NavigationSplitView(ap).md */; };
08D4EBDB2BF4599D0031EDC5 /* Inspectors右侧多出一栏(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D4EBDA2BF4599D0031EDC5 /* Inspectors右侧多出一栏(ap).md */; };
08D4EBDD2BF461F60031EDC5 /* 自定义导航栏(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D4EBDC2BF461F60031EDC5 /* 自定义导航栏(ap).md */; };
08D4EBDF2BF497030031EDC5 /* 导航状态保存和还原(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D4EBDE2BF497030031EDC5 /* 导航状态保存和还原(ap).md */; };
08D4EBE22BF4A76A0031EDC5 /* 布局-基础(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D4EBE12BF4A76A0031EDC5 /* 布局-基础(ap).md */; };
08D4EBE42BF4AB9A0031EDC5 /* 布局-留白(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D4EBE32BF4AB9A0031EDC5 /* 布局-留白(ap).md */; };
08D4EBE62BF4B8050031EDC5 /* 布局-对齐(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D4EBE52BF4B8050031EDC5 /* 布局-对齐(ap).md */; };
08D8EFE52BED825E00AA0020 /* BookmarkListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08D8EFE42BED825E00AA0020 /* BookmarkListView.swift */; };
08D8EFE92BEEF68800AA0020 /* 小组件-StaticConfiguration(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D8EFE82BEEF68800AA0020 /* 小组件-StaticConfiguration(ap).md */; };
08D8EFEB2BEF106B00AA0020 /* 小组件-AppIntentConfiguration(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 08D8EFEA2BEF106B00AA0020 /* 小组件-AppIntentConfiguration(ap).md */; };
Expand Down Expand Up @@ -487,6 +494,13 @@
08D4EBD22BF437510031EDC5 /* Navigation(ap).md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "Navigation(ap).md"; sourceTree = "<group>"; };
08D4EBD42BF4379E0031EDC5 /* NavigationStack(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "NavigationStack(ap).md"; sourceTree = "<group>"; };
08D4EBD62BF43C540031EDC5 /* NavigationPath(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "NavigationPath(ap).md"; sourceTree = "<group>"; };
08D4EBD82BF44C170031EDC5 /* NavigationSplitView(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "NavigationSplitView(ap).md"; sourceTree = "<group>"; };
08D4EBDA2BF4599D0031EDC5 /* Inspectors右侧多出一栏(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Inspectors右侧多出一栏(ap).md"; sourceTree = "<group>"; };
08D4EBDC2BF461F60031EDC5 /* 自定义导航栏(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "自定义导航栏(ap).md"; sourceTree = "<group>"; };
08D4EBDE2BF497030031EDC5 /* 导航状态保存和还原(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "导航状态保存和还原(ap).md"; sourceTree = "<group>"; };
08D4EBE12BF4A76A0031EDC5 /* 布局-基础(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "布局-基础(ap).md"; sourceTree = "<group>"; };
08D4EBE32BF4AB9A0031EDC5 /* 布局-留白(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "布局-留白(ap).md"; sourceTree = "<group>"; };
08D4EBE52BF4B8050031EDC5 /* 布局-对齐(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "布局-对齐(ap).md"; sourceTree = "<group>"; };
08D8EFE42BED825E00AA0020 /* BookmarkListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkListView.swift; sourceTree = "<group>"; };
08D8EFE82BEEF68800AA0020 /* 小组件-StaticConfiguration(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "小组件-StaticConfiguration(ap).md"; sourceTree = "<group>"; };
08D8EFEA2BEF106B00AA0020 /* 小组件-AppIntentConfiguration(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "小组件-AppIntentConfiguration(ap).md"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -943,6 +957,10 @@
08D4EBD22BF437510031EDC5 /* Navigation(ap).md */,
08D4EBD42BF4379E0031EDC5 /* NavigationStack(ap).md */,
08D4EBD62BF43C540031EDC5 /* NavigationPath(ap).md */,
08D4EBD82BF44C170031EDC5 /* NavigationSplitView(ap).md */,
08D4EBDC2BF461F60031EDC5 /* 自定义导航栏(ap).md */,
08D4EBDA2BF4599D0031EDC5 /* Inspectors右侧多出一栏(ap).md */,
08D4EBDE2BF497030031EDC5 /* 导航状态保存和还原(ap).md */,
);
path = "Navigation导航";
sourceTree = "<group>";
Expand Down Expand Up @@ -1140,6 +1158,7 @@
isa = PBXGroup;
children = (
0850AC232BF436E8009FDBBF /* Navigation导航 */,
08D4EBE02BF4A7470031EDC5 /* 布局基础 */,
08C3BB7F27CE4A8500ACF0FE /* TabView(ap).md */,
08BE634927C4BDDB002BC6A8 /* Stack(ap).md */,
08BE635727C63F3A002BC6A8 /* ControlGroup(ap).md */,
Expand Down Expand Up @@ -1192,6 +1211,16 @@
path = Core;
sourceTree = "<group>";
};
08D4EBE02BF4A7470031EDC5 /* 布局基础 */ = {
isa = PBXGroup;
children = (
08D4EBE12BF4A76A0031EDC5 /* 布局-基础(ap).md */,
08D4EBE32BF4AB9A0031EDC5 /* 布局-留白(ap).md */,
08D4EBE52BF4B8050031EDC5 /* 布局-对齐(ap).md */,
);
path = "布局基础";
sourceTree = "<group>";
};
08D8EFE32BED74D900AA0020 /* View */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1494,8 +1523,10 @@
0869233A2BF1A490006779A3 /* scrollTransition视觉效果(ap).md in Resources */,
08659BC72BE8FD84009B7C00 /* SwiftUI数据流(ap).md in Resources */,
08449011279ECC3E00B61353 /* Combine KVO(ap).md in Resources */,
08D4EBDF2BF497030031EDC5 /* 导航状态保存和还原(ap).md in Resources */,
08448FE2279EC7CF00B61353 /* Nil-coalescing(ap).md in Resources */,
08449002279ECB0500B61353 /* append(ap).md in Resources */,
08D4EBDB2BF4599D0031EDC5 /* Inspectors右侧多出一栏(ap).md in Resources */,
08449013279ECC6300B61353 /* Combine通知(ap).md in Resources */,
0844901E279ECD9D00B61353 /* 结构化并发(ap).md in Resources */,
08448FD3279EC60300B61353 /* 数组(ap).md in Resources */,
Expand Down Expand Up @@ -1559,6 +1590,7 @@
0850AC082BF2E63C009FDBBF /* List-搜索(ap).md in Resources */,
08448F71279EB58C00B61353 /* Data(ap).md in Resources */,
08448FFE279ECAA100B61353 /* removeDuplicates(ap).md in Resources */,
08D4EBE62BF4B8050031EDC5 /* 布局-对齐(ap).md in Resources */,
08D8EFEB2BEF106B00AA0020 /* 小组件-AppIntentConfiguration(ap).md in Resources */,
0850445827B1228E0096D556 /* Result(ap).md in Resources */,
08449008279ECB6500B61353 /* zip(ap).md in Resources */,
Expand All @@ -1574,13 +1606,15 @@
086923362BF18918006779A3 /* 滚动到特定的位置(ap).md in Resources */,
08448F73279EB5DF00B61353 /* 文件(ap).md in Resources */,
08448FC8279EC54300B61353 /* If(ap).md in Resources */,
08D4EBE22BF4A76A0031EDC5 /* 布局-基础(ap).md in Resources */,
08448F7E279EB71D00B61353 /* 单例(ap).md in Resources */,
0850AC1C2BF3A333009FDBBF /* Table-样式(ap).md in Resources */,
08448F8F279EB88500B61353 /* Keychain(ap).md in Resources */,
08448FA0279EBAD900B61353 /* 访问控制(ap).md in Resources */,
0850AC0C2BF2FA2F009FDBBF /* List-轻扫操作(ap).md in Resources */,
08448FFA279ECA5300B61353 /* Empty(ap).md in Resources */,
08026C462869B26000792EF1 /* Swift-DocC(ap).md in Resources */,
08D4EBE42BF4AB9A0031EDC5 /* 布局-留白(ap).md in Resources */,
08448FF8279ECA2000B61353 /* PassthroughSubject(ap).md in Resources */,
08D8EFF82BEF912700AA0020 /* 小组件动画(ap).md in Resources */,
08D8F0042BEFA86C00AA0020 /* 小组件-参考资料(ap).md in Resources */,
Expand Down Expand Up @@ -1619,6 +1653,7 @@
08448FE8279EC84B00B61353 /* 恒等(ap).md in Resources */,
08026C4C2869B39F00792EF1 /* Advanced layout control(ap).md in Resources */,
086A5F0E2744E89100FECE02 /* Preview Assets.xcassets in Resources */,
08D4EBD92BF44C170031EDC5 /* NavigationSplitView(ap).md in Resources */,
08448F0F2799328700B61353 /* css_cn.html in Resources */,
086923312BF171A6006779A3 /* ForEach(ap).md in Resources */,
08448F5A279EA84100B61353 /* 三栏结构(ap).md in Resources */,
Expand All @@ -1627,6 +1662,7 @@
08D8F0002BEFA72300AA0020 /* 获取小组件形状(ap).md in Resources */,
08026C4E2869B3B500792EF1 /* ShareLink(ap).md in Resources */,
08026C432869B22E00792EF1 /* Regex(ap).md in Resources */,
08D4EBDD2BF461F60031EDC5 /* 自定义导航栏(ap).md in Resources */,
08BE636427C886D2002BC6A8 /* LazyVStack和LazyHStack(ap).md in Resources */,
08449030279ECF7D00B61353 /* 1.md in Resources */,
08522BDA27CF5029005FF059 /* Slider(ap).md in Resources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ struct DeveloperListView: View {
Spacer()
}
}
.listRowSeparator(.hidden, edges: .all)
.tag(dev)
.swipeActions {
Button(role: .destructive) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,5 @@ struct BookmarkListView: View {
}
} // end overlay
}

}
15 changes: 12 additions & 3 deletions SwiftPamphletApp/Guide/View/GuideListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ final class GuideListModel {
L(t: "TextField"),
L(t: "Image"),
]),
L(t: "数据集合组件", sub: [
L(t: "数据集合组件",icon: "list.bullet.rectangle.fill", sub: [
L(t: "ForEach"),
L(t: "Scroll视图", icon: "scroll.fill", sub: [
L(t: "ScrollView"),
Expand Down Expand Up @@ -252,11 +252,20 @@ final class GuideListModel {
L(t: "Table-contextMenu", icon: "filemenu.and.selection"),
]),
]),
L(t: "布局组件", sub: [
L(t: "布局组件",icon: "rectangle.3.group.fill", sub: [
L(t: "Navigation导航", icon: "sidebar.squares.leading", sub: [
L(t: "Navigation", icon: "sidebar.squares.leading"),
L(t: "NavigationStack", icon: "square.stack.3d.down.forward"),
L(t: "NavigationPath"),
L(t: "NavigationPath", icon: "arrow.3.trianglepath"),
L(t: "NavigationSplitView", icon: "rectangle.split.3x1"),
L(t: "自定义导航栏"),
L(t: "Inspectors右侧多出一栏", icon: "rectangle.split.3x1"),
L(t: "导航状态保存和还原"),
]),
L(t: "布局基础",icon: "rectangle.3.group", sub: [
L(t: "布局-基础"),
L(t: "布局-留白"),
L(t: "布局-对齐"),
]),
L(t: "TabView"),
L(t: "Stack", icon: "square.3.layers.3d"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@

Inspector 的示例

```swift
struct Book: Identifiable {
var id = UUID()
var title: String
var author: String
var description: String
}

struct ContentView: View {
@State var books: [Book] = [
Book(title: "Book 1", author: "Author 1", description: "Description 1"),
Book(title: "Book 2", author: "Author 2", description: "Description 2"),
Book(title: "Book 3", author: "Author 3", description: "Description 3")
]
@State var selectedBook: Book?
@State var showInspector: Bool = false
@State var splitVisibility: NavigationSplitViewVisibility = .all

var body: some View {
NavigationSplitView(columnVisibility: $splitVisibility, sidebar: {
List(books) { book in
Button(action: { selectedBook = book }) {
Text(book.title)
}
}
}, content: {
if let book = selectedBook {
Text("Author: \(book.author)")
} else {
Text("Select a Book")
}
}, detail: {
Button("Inspector 开关") {
showInspector.toggle()
}
if let book = selectedBook {
Text(book.description)
} else {
Text("Book details will appear here")
}
})
.inspector(isPresented: $showInspector) {
if let book = selectedBook {
InspectorView(book: book)
}
}
}
}

struct InspectorView: View {
var book: Book

var body: some View {
VStack {
Text(book.title).font(.largeTitle)
Text("Author: \(book.author)").font(.title)
Text(book.description).padding()
}
.inspectorColumnWidth(200)
.presentationDetents([.medium, .large])
}
}
```

它显示了一个图书列表。当用户选择一个图书时,会显示 InspectorView,这是辅助视图,它显示了图书的详细信息。inspector 方法用于显示和隐藏 InspectorView,inspectorColumnWidth 方法用于设置辅助视图的宽度,presentationDetents 方法用于设置辅助视图的大小。
Original file line number Diff line number Diff line change
@@ -1,2 +1,80 @@

## 使用说明
`NavigationPath` 是一个用于管理 SwiftUI 中导航路径的工具。它可以帮助你在 SwiftUI 中实现更复杂的导航逻辑。

在 SwiftUI 中,我们通常使用 `NavigationLink` 来实现导航。然而,`NavigationLink` 只能实现简单的前进导航,如果你需要实现更复杂的导航逻辑,例如后退、跳转到任意页面等,你就需要使用 `NavigationPath`

`NavigationPath` 的工作原理是,它维护了一个路径数组,每个元素代表一个页面。当你需要导航到一个新的页面时,你只需要将这个页面添加到路径数组中。当你需要后退时,你只需要从路径数组中移除最后一个元素。这样,你就可以实现任意复杂的导航逻辑。

看个例子

假设我们有一个 TVShow 结构体,它包含电视剧的名字。当用户点击一个电视剧的名字时,他们会被导航到这个电视剧的详细信息页面。

```swift
struct ContentView: View {
@State private var path = NavigationPath()
@State private var tvShows = [ TVShow(name: "Game of Thrones"), TVShow(name: "Breaking Bad"), TVShow(name: "The Witcher") ]

var body: some View {
NavigationStack(path: $path) {
List {
Text("Select a TV show to get started.")
.font(.subheadline.weight(.semibold))
ForEach(tvShows, id: \.name) { show in
NavigationLink(value: show, label: {
Text(show.name)
.font(.subheadline.weight(.medium))
})
}
Button(action: showFriends) {
Text("This isn't navigation")
}
}
.navigationDestination(for: TVShow.self, destination: { show in
TVShowView(onSelectReset: { popToRoot() }, show: show, otherShows: tvShows)
})
.navigationTitle(Text("Select your show"))
}
.onChange(of: path.count) { oldValue, newValue in
print(newValue)
}
}

func showFriends() {
let show = TVShow(name: "Friends")
path.append(show)
}

func popToRoot() {
path.removeLast(path.count)
}
}

struct TVShowView: View {
var onSelectReset: () -> Void
var show: TVShow
var otherShows: [TVShow]

var body: some View {
VStack {
Text(show.name)
.font(.title)
.padding(.bottom)
Button(action: onSelectReset) {
Text("Reset Selection")
}
List(otherShows, id: \.name) { otherShow in
Text(otherShow.name)
}
}
.padding()
}
}

struct TVShow: Hashable {
let name: String
let premiereDate: Date = Date.now
var description: String = "detail"
}
```

代码中,`NavigationPath` 被用作一个 `@State` 变量,这意味着它会自动响应变化,并更新视图。当你修改 `NavigationPath` 中的路径数组时,视图会自动更新,显示新的页面。
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

以下是一个基于 NavigationSplitView 的三栏视图的示例。这个示例包含了一个主视图,一个次级视图和一个详细视图。

```swift
struct ContentView: View {
@State var books: [Book] = [
Book(title: "Book 1", author: "Author 1", description: "Description 1"),
Book(title: "Book 2", author: "Author 2", description: "Description 2"),
Book(title: "Book 3", author: "Author 3", description: "Description 3")
]
@State var selectedBook: Book?
@State var splitVisibility: NavigationSplitViewVisibility = .all

var body: some View {
NavigationSplitView(columnVisibility: $splitVisibility, sidebar: {
List(books) { book in
Button(action: { selectedBook = book }) {
Text(book.title)
}
}
}, content: {
if let book = selectedBook {
Text("Author: \(book.author)")
} else {
Text("Select a Book")
}
}, detail: {
if let book = selectedBook {
Text(book.description)
} else {
Text("Book details will appear here")
}
})
.onChange(of: selectedBook) { oldValue, newValue in
//...
}
}
}

struct Book: Identifiable, Equatable {
var id = UUID()
var title: String
var author: String
var description: String
}
```

示例中,`sidebar` 是主视图,它显示了一个图书列表。当用户选择一个图书时,`content` 视图会显示图书的作者,`detail` 视图会显示图书的详细信息。`NavigationSplitView` 会根据 `splitVisibility` 的值来决定显示哪些视图。
Loading

0 comments on commit 04a2aa0

Please sign in to comment.