Skip to content

Commit

Permalink
新增布局内容
Browse files Browse the repository at this point in the history
  • Loading branch information
ming1016 committed May 16, 2024
1 parent 00b6a76 commit 611bab1
Show file tree
Hide file tree
Showing 11 changed files with 465 additions and 6 deletions.
40 changes: 36 additions & 4 deletions SwiftPamphletApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,12 @@
086A5F442744EE2800FECE02 /* SwiftPamphletAppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086A5F432744EE2800FECE02 /* SwiftPamphletAppConfig.swift */; };
086A5F462744EEB900FECE02 /* FundationFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086A5F452744EEB900FECE02 /* FundationFunction.swift */; };
086A5F522744EF4C00FECE02 /* ViewComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086A5F512744EF4C00FECE02 /* ViewComponent.swift */; };
086BEEF82BF629DB00025307 /* AnyLayout(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086BEEF72BF629DB00025307 /* AnyLayout(ap).md */; };
086BEEFA2BF6300400025307 /* ViewThatFits(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086BEEF92BF6300400025307 /* ViewThatFits(ap).md */; };
086BEEFC2BF63A0000025307 /* Layout协议(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086BEEFB2BF63A0000025307 /* Layout协议(ap).md */; };
086BEEFE2BF644D400025307 /* GeometryReader(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086BEEFD2BF644D400025307 /* GeometryReader(ap).md */; };
086BEF002BF659E500025307 /* alignmentGuide(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086BEEFF2BF659E500025307 /* alignmentGuide(ap).md */; };
086BEF022BF65DCD00025307 /* 布局进阶-参考资料(ap).md in Resources */ = {isa = PBXBuildFile; fileRef = 086BEF012BF65DCD00025307 /* 布局进阶-参考资料(ap).md */; };
0871C6192BA040E5000B620D /* InfoRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0871C6182BA040E5000B620D /* InfoRowView.swift */; };
0871C61B2BA04D23000B620D /* CategoryRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0871C61A2BA04D23000B620D /* CategoryRowView.swift */; };
0871C61D2BA05F44000B620D /* InfosView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0871C61C2BA05F44000B620D /* InfosView.swift */; };
Expand Down Expand Up @@ -465,6 +471,12 @@
086A5F432744EE2800FECE02 /* SwiftPamphletAppConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftPamphletAppConfig.swift; sourceTree = "<group>"; };
086A5F452744EEB900FECE02 /* FundationFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FundationFunction.swift; sourceTree = "<group>"; };
086A5F512744EF4C00FECE02 /* ViewComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewComponent.swift; sourceTree = "<group>"; };
086BEEF72BF629DB00025307 /* AnyLayout(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "AnyLayout(ap).md"; sourceTree = "<group>"; };
086BEEF92BF6300400025307 /* ViewThatFits(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "ViewThatFits(ap).md"; sourceTree = "<group>"; };
086BEEFB2BF63A0000025307 /* Layout协议(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "Layout协议(ap).md"; sourceTree = "<group>"; };
086BEEFD2BF644D400025307 /* GeometryReader(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "GeometryReader(ap).md"; sourceTree = "<group>"; };
086BEEFF2BF659E500025307 /* alignmentGuide(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "alignmentGuide(ap).md"; sourceTree = "<group>"; };
086BEF012BF65DCD00025307 /* 布局进阶-参考资料(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "布局进阶-参考资料(ap).md"; sourceTree = "<group>"; };
086D48A02BBB820100835544 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
0871C6182BA040E5000B620D /* InfoRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoRowView.swift; sourceTree = "<group>"; };
0871C61A2BA04D23000B620D /* CategoryRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryRowView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1126,6 +1138,19 @@
path = ViewComponet;
sourceTree = "<group>";
};
086BEEF62BF629C300025307 /* 布局进阶 */ = {
isa = PBXGroup;
children = (
086BEEF72BF629DB00025307 /* AnyLayout(ap).md */,
086BEEF92BF6300400025307 /* ViewThatFits(ap).md */,
086BEEFB2BF63A0000025307 /* Layout协议(ap).md */,
086BEEFD2BF644D400025307 /* GeometryReader(ap).md */,
086BEEFF2BF659E500025307 /* alignmentGuide(ap).md */,
086BEF012BF65DCD00025307 /* 布局进阶-参考资料(ap).md */,
);
path = "布局进阶";
sourceTree = "<group>";
};
0887A5982BA28F3600131359 /* Guide */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1167,6 +1192,7 @@
children = (
0850AC232BF436E8009FDBBF /* Navigation导航 */,
08D4EBE02BF4A7470031EDC5 /* 布局基础 */,
086BEEF62BF629C300025307 /* 布局进阶 */,
08BE634927C4BDDB002BC6A8 /* Stack(ap).md */,
08BE635B27C65C7C002BC6A8 /* GroupBox(ap).md */,
08C3BB7F27CE4A8500ACF0FE /* TabView(ap).md */,
Expand Down Expand Up @@ -1496,6 +1522,7 @@
08659BDF2BEA4D8C009B7C00 /* SwiftData-模型关系(ap).md in Resources */,
08D8EFFC2BEF9EDE00AA0020 /* 小组件-获取位置权限更新内容(ap).md in Resources */,
084E1A6327B517FC0072BBB6 /* Swift各版本演进(ap).md in Resources */,
086BEEFA2BF6300400025307 /* ViewThatFits(ap).md in Resources */,
08448F6F279EB56400B61353 /* 度量值(ap).md in Resources */,
0844900C279ECBB400B61353 /* Scheduler(ap).md in Resources */,
08448FA3279EBB1B00B61353 /* 数字(ap).md in Resources */,
Expand Down Expand Up @@ -1525,10 +1552,12 @@
08026C472869B26900792EF1 /* 调试(ap).md in Resources */,
08448F8C279EB84800B61353 /* 布局动画(ap).md in Resources */,
08448FC1279EC4B500B61353 /* filter(ap).md in Resources */,
086BEEFC2BF63A0000025307 /* Layout协议(ap).md in Resources */,
08448F9C279EBA8200B61353 /* 闭包(ap).md in Resources */,
0869233C2BF1BF35006779A3 /* ScrollView-参考资料(ap).md in Resources */,
08448FAF279EC31200B61353 /* 不透明类型(ap).md in Resources */,
08449029279ECEB100B61353 /* SwiftUI是什么(ap).md in Resources */,
086BEEFE2BF644D400025307 /* GeometryReader(ap).md in Resources */,
08448F79279EB68D00B61353 /* 随机(ap).md in Resources */,
08BE632A27BE220D002BC6A8 /* 全屏模式(ap).md in Resources */,
08448F5B279EA84100B61353 /* macOS共享菜单(ap).md in Resources */,
Expand Down Expand Up @@ -1595,6 +1624,7 @@
0850AC062BF2E5DA009FDBBF /* List-移动元素(ap).md in Resources */,
08659BCD2BE9A40A009B7C00 /* 创建@Model模型(ap).md in Resources */,
08449000279ECAE100B61353 /* flatMap(ap).md in Resources */,
086BEF002BF659E500025307 /* alignmentGuide(ap).md in Resources */,
08D8EFFE2BEFA3E300AA0020 /* 支持多个小组件(ap).md in Resources */,
086923382BF19AB7006779A3 /* scrollTargetBehavior分页滚动(ap).md in Resources */,
08448FAB279EC2B400B61353 /* 枚举(ap).md in Resources */,
Expand All @@ -1618,6 +1648,7 @@
085BB77627D22FE300E8F69A /* SwiftUI Canvas(ap).md in Resources */,
08BE635827C63F3A002BC6A8 /* ControlGroup(ap).md in Resources */,
08BE637427CCAB52002BC6A8 /* ScrollView(ap).md in Resources */,
086BEF022BF65DCD00025307 /* 布局进阶-参考资料(ap).md in Resources */,
086923362BF18918006779A3 /* 滚动到特定的位置(ap).md in Resources */,
08448F73279EB5DF00B61353 /* 文件(ap).md in Resources */,
08448FC8279EC54300B61353 /* If(ap).md in Resources */,
Expand Down Expand Up @@ -1669,6 +1700,7 @@
08448FE8279EC84B00B61353 /* 恒等(ap).md in Resources */,
08026C4C2869B39F00792EF1 /* Advanced layout control(ap).md in Resources */,
086A5F0E2744E89100FECE02 /* Preview Assets.xcassets in Resources */,
086BEEF82BF629DB00025307 /* AnyLayout(ap).md in Resources */,
08D4EBD92BF44C170031EDC5 /* NavigationSplitView(ap).md in Resources */,
08448F0F2799328700B61353 /* css_cn.html in Resources */,
086923312BF171A6006779A3 /* ForEach(ap).md in Resources */,
Expand Down Expand Up @@ -1883,7 +1915,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 10;
CURRENT_PROJECT_VERSION = 11;
DEVELOPMENT_ASSET_PATHS = "\"SwiftPamphletApp/Preview Content\"";
DEVELOPMENT_TEAM = 962Z8PV35L;
ENABLE_HARDENED_RUNTIME = YES;
Expand All @@ -1897,7 +1929,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 6.4.5;
MARKETING_VERSION = 6.4.6;
OTHER_LDFLAGS = (
"-Xlinker",
"-interposable",
Expand Down Expand Up @@ -1926,7 +1958,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 10;
CURRENT_PROJECT_VERSION = 11;
DEVELOPMENT_ASSET_PATHS = "\"SwiftPamphletApp/Preview Content\"";
DEVELOPMENT_TEAM = 962Z8PV35L;
ENABLE_HARDENED_RUNTIME = YES;
Expand All @@ -1940,7 +1972,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.0;
MARKETING_VERSION = 6.4.5;
MARKETING_VERSION = 6.4.6;
PRODUCT_BUNDLE_IDENTIFIER = com.starming.SwiftPamphletAppByMing;
PRODUCT_NAME = "戴铭的开发小册子";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down
8 changes: 8 additions & 0 deletions SwiftPamphletApp/Guide/View/GuideListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,14 @@ final class GuideListModel {
L(t: "Safe Area"),
L(t: "布局原理"),
]),
L(t: "布局进阶", sub: [
L(t: "AnyLayout"),
L(t: "ViewThatFits"),
L(t: "Layout协议"),
L(t: "GeometryReader"),
L(t: "alignmentGuide"),
L(t: "布局进阶-参考资料"),
]),
L(t: "Stack", icon: "square.3.layers.3d"),
L(t: "GroupBox", icon: "shippingbox"),
L(t: "TabView"),
Expand Down
5 changes: 3 additions & 2 deletions SwiftPamphletApp/InfoOrganizer/Info/InfoRowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ struct InfoRowView: View {
if let coverImg = info.coverImage {
ZStack {
Rectangle()
.fill(Color.clear)
.fill(Color(light: .white, dark: .black))
.frame(height: 80)
.cornerRadius(5)
GeometryReader { geometry in
if coverImg.url.isEmpty == false {
NukeImage(width: geometry.size.width, height: geometry.size.height, url: coverImg.url, contentModel: .fill)
Expand All @@ -34,7 +35,7 @@ struct InfoRowView: View {
}
}
}
.shadow(color: Color(.sRGB, red: 0, green: 0, blue: 0, opacity: 0.9), radius: 1, x: 0, y: 0)
.shadow(color: Color(.sRGB, red: 0, green: 0, blue: 0, opacity: 0.3), radius: 1, x: 0, y: 0)
}
if info.url.isEmpty == false {
HStack {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@


## ignoresSafeArea 忽略安全区域

使用 `.ignoresSafeArea()` 可以忽略安全区域。默认是所有方向都忽略。

如果只忽略部分方向,可以按照下面方法做:
Expand All @@ -14,3 +17,89 @@
.ignoresSafeArea(.container, edges: [.leading, .trailing])
```

## safeAreaInset

`safeAreaInset` 是 SwiftUI 中的一个属性,它允许你将视图放置在安全区域内。"安全区域"是指设备屏幕上的一块区域,这块区域不会被系统界面(如状态栏、导航栏、工具栏、Tab栏等)遮挡。

例如,你可以使用 `safeAreaInset` 将一个视图放置在屏幕底部的安全区域内,代码如下:

```swift
VStack {
Text("Hello, World!")
}
.safeAreaInset(edge: .bottom, spacing: 10) {
Button("Press me") {
print("Button pressed")
}
}
```

在这个例子中,"Press me" 按钮会被放置在屏幕底部的安全区域内,而且距离底部有 10 个点的间距。

下面是更完整点的例子:

```swift
struct ContentView: View {
@State var tasks: [TaskModel] = (0...10).map { TaskModel(name: "Task \($0)") }
@State var taskName = ""
@State var isFocused: Bool = false

var body: some View {
NavigationView {
VStack {
List {
ForEach(tasks) { task in
Text(task.name)
}
}
.listStyle(PlainListStyle())
.safeAreaInset(edge: .bottom) {
HStack {
TextField("Add task", text: $taskName, onCommit: {
addTask()
})
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding(.leading, 10)

Button(action: {
addTask()
}) {
Image(systemName: "plus")
}
.padding(.trailing, 10)
}
.padding(.bottom, isFocused ? 0 : 10)
.background(Color.white)
}
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { _ in
withAnimation {
isFocused = true
}
}
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in
withAnimation {
isFocused = false
}
}
}
.navigationBarTitle("Task List Demo")
}
}

func addTask() {
if !taskName.isEmpty {
withAnimation {
tasks.append(TaskModel(name: taskName))
}
taskName = ""
}
}
}

struct TaskModel: Identifiable {
let id = UUID()
let name: String
}
```

用户可以在底部的输入框中输入任务名称,然后点击 "+" 按钮将任务添加到任务清单中。添加的任务会显示在屏幕的上方。当键盘出现或消失时,底部的输入框会相应地移动,以确保不会被键盘遮挡。
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@

使用 AnyLayout 包装布局组件,可以在布局之间进行切换,同时保持动画效果。

```swift
struct WeatherLayout: View {
@State private var changeLayout = false

var body: some View {
let layout = changeLayout ? AnyLayout(HStackLayout()) : AnyLayout(VStackLayout())

layout {
WeatherView(icon: "sun.max.fill", temperature: 25, color: .yellow)
WeatherView(icon: "cloud.rain.fill", temperature: 18, color: .blue)
WeatherView(icon: "snow", temperature: -5, color: .white)
}
.animation(.default, value: changeLayout)
.onTapGesture {
changeLayout.toggle()
}
}
}

struct WeatherView: View {
let icon: String
let temperature: Int
let color: Color

var body: some View {
VStack {
Image(systemName: icon)
.font(.system(size: 80))
.foregroundColor(color)
Text("\(temperature)°")
.font(.system(size: 50))
.foregroundColor(color)
}
.frame(width: 120, height: 120)
}
}
```

代码中,我们创建了一个 WeatherView 视图,它包含一个天气图标和一个温度标签。然后,我们在 WeatherLayout 视图中使用 AnyLayout 来动态改变布局。用户可以通过点击视图来在水平布局和垂直布局之间切换。
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@


在 SwiftUI 中,有多种方法可以获取和控制视图的尺寸:

- `frame(width:60, height:60)`:这个方法会为子视图提供一个建议的尺寸,这里是 60 x 60。
- `fixedSize()`:这个方法会为子视图提供一个未指定模式的建议尺寸,这意味着视图会尽可能地大以适应其内容。
- `frame(minWidth: 120, maxWidth: 360)`:这个方法会将子视图的需求尺寸控制在指定的范围中,这里是宽度在 120 到 360 之间。
- `frame(idealWidth: 120, idealHeight: 120)`:这个方法会返回一个需求尺寸,如果当前视图收到为未指定模式的建议尺寸,那么它会返回 120 x 120 的尺寸。
- `GeometryReader``GeometryReader` 会将建议尺寸作为需求尺寸直接返回,这意味着它会充满全部可用区域。你可以使用 `GeometryReader` 来获取其内容的尺寸和位置。


`GeometryReader` 可以获取其内容的尺寸和位置。在这个例子中,我们使用 `GeometryReader` 来获取视图的尺寸,然后打印出来。这对于理解 SwiftUI 的布局系统和调试布局问题非常有用。

```swift
extension View {
func logSizeInfo(_ label: String = "") -> some View {
background(
GeometryReader { proxy in
Color.clear
.onAppear(perform: {
debugPrint("\(label) Size: \(proxy.size)")
})
}
)
}
}

struct ContentView: View {
var body: some View {
VStack {
Text("大标题")
.font(.largeTitle)
.logSizeInfo("大标题视图") // 打印视图尺寸
Text("正文")
.logSizeInfo("正文视图")
}
}
}
```

这段代码首先定义了一个 `View` 的扩展,添加了一个 `logSizeInfo(_:)` 方法。这个方法接受一个标签字符串作为参数,然后返回一个新的视图。这个新的视图在背景中使用 `GeometryReader` 来获取并打印视图的尺寸。

然后,我们创建了一个 `VStack` 视图,其中包含一个 `Text` 视图。我们为 `Text` 视图调用了 `logSizeInfo(_:)` 方法,以打印其尺寸。


如何利用 `GeometryReader` 来绘制一个圆形?

```swift
struct CircleView: View {
var body: some View {
GeometryReader { proxy in
Path { path in
let radius = min(proxy.size.width, proxy.size.height) / 2
let center = CGPoint(x: proxy.size.width / 2, y: proxy.size.height / 2)
path.addArc(center: center, radius: radius, startAngle: .zero, endAngle: .init(degrees: 360), clockwise: false)
}
.fill(Color.blue)
}
}
}
```

在这个例子中,我们首先获取 `GeometryReader` 的尺寸,然后计算出半径和中心点的位置。然后,我们使用 `Path``addArc(center:radius:startAngle:endAngle:clockwise:)` 方法来添加一个圆形路径。最后,我们使用 `fill(_:)` 方法来填充路径,颜色为蓝色。

关于 GeometryReader 性能问题

GeometryReader 是 SwiftUI 中的一个工具,它可以帮助我们获取视图的大小和位置。但是,它在获取这些信息时,需要等待视图被评估、布局和渲染完成。这就好比你在装修房子时,需要等待墙壁砌好、油漆干燥后,才能测量墙壁的尺寸。这个过程可能需要等待一段时间,而且可能需要多次重复,因为每次墙壁的尺寸改变,都需要重新测量。

这就是 GeometryReader 可能会影响性能的原因。它需要等待视图完成一轮的评估、布局和渲染,然后才能获取到尺寸数据,然后可能需要根据这些数据重新调整布局,这就需要再次进行评估、布局和渲染。这个过程可能需要重复多次,导致视图被多次重新评估和布局。

但是,随着 SwiftUI 的更新,这个问题已经有所改善。现在,我们可以创建自定义的布局容器,这些容器可以在布局阶段就获取到父视图的建议尺寸和所有子视图的需求尺寸,这样就可以避免反复传递尺寸数据,减少了视图的反复更新。
Loading

0 comments on commit 611bab1

Please sign in to comment.