自定义分段控制器 SwiftUI 框架问题
Posted
技术标签:
【中文标题】自定义分段控制器 SwiftUI 框架问题【英文标题】:Custom Segmented Controller SwiftUI Frame Issue 【发布时间】:2021-03-07 02:48:08 【问题描述】:我想在 SwiftUI 中创建一个自定义的分段控制器,我发现了一个由 post 制成的控制器。在稍微更改代码并将其放入我的 ContentView 后,彩色胶囊将无法正确匹配。
这是我想要的结果的示例:
这是我在 ContentView 中使用时的结果:
CustomPicker.swift:
struct CustomPicker: View
@State var selectedIndex = 0
var titles = ["Item #1", "Item #2", "Item #3", "Item #4"]
private var colors = [Color.red, Color.green, Color.blue, Color.purple]
@State private var frames = Array<CGRect>(repeating: .zero, count: 4)
var body: some View
VStack
ZStack
HStack(spacing: 4)
ForEach(self.titles.indices, id: \.self) index in
Button(action: self.selectedIndex = index )
Text(self.titles[index])
.foregroundColor(.black)
.font(.system(size: 16, weight: .medium, design: .default))
.bold()
.padding(EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16)).background(
GeometryReader geo in
Color.clear.onAppear self.setFrame(index: index, frame: geo.frame(in: .global))
)
.background(
Capsule().fill(
self.colors[self.selectedIndex].opacity(0.4))
.frame(width: self.frames[self.selectedIndex].width,
height: self.frames[self.selectedIndex].height, alignment: .topLeading)
.offset(x: self.frames[self.selectedIndex].minX - self.frames[0].minX)
, alignment: .leading
)
.animation(.default)
.background(Capsule().stroke(Color.gray, lineWidth: 3))
func setFrame(index: Int, frame: CGRect)
self.frames[index] = frame
ContentView.swift:
struct ContentView: View
@State var itemsList = [Item]()
func loadData()
if let url = Bundle.main.url(forResource: "Data", withExtension: "json")
do
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
let jsonData = try decoder.decode(Response.self, from: data)
for post in jsonData.content
self.itemsList.append(post)
catch
print("error:\(error)")
var body: some View
NavigationView
VStack
Text("Item picker")
.font(.system(.title))
.bold()
CustomPicker()
Spacer()
ScrollView
VStack
ForEach(itemsList) item in
ItemView(text: item.text, username: item.username)
.padding(.leading)
.frame(height: UIScreen.screenHeight - 224)
.onAppear(perform: loadData)
Project file here
【问题讨论】:
【参考方案1】:编写代码的问题是GeometryReader
值仅在onAppear
上发送。这意味着如果它周围的任何视图发生变化并且视图被重新渲染(例如在加载数据时),这些框架将过期。
我通过使用 PreferenceKey
解决了这个问题,它将在每个渲染上运行:
struct CustomPicker: View
@State var selectedIndex = 0
var titles = ["Item #1", "Item #2", "Item #3", "Item #4"]
private var colors = [Color.red, Color.green, Color.blue, Color.purple]
@State private var frames = Array<CGRect>(repeating: .zero, count: 4)
var body: some View
VStack
ZStack
HStack(spacing: 4)
ForEach(self.titles.indices, id: \.self) index in
Button(action: self.selectedIndex = index )
Text(self.titles[index])
.foregroundColor(.black)
.font(.system(size: 16, weight: .medium, design: .default))
.bold()
.padding(EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16))
.measure() // <-- Here
.onPreferenceChange(FrameKey.self, perform: value in
self.setFrame(index: index, frame: value) //<-- this will run each time the preference value changes, will will happen any time the frame is updated
)
.background(
Capsule().fill(
self.colors[self.selectedIndex].opacity(0.4))
.frame(width: self.frames[self.selectedIndex].width,
height: self.frames[self.selectedIndex].height, alignment: .topLeading)
.offset(x: self.frames[self.selectedIndex].minX - self.frames[0].minX)
, alignment: .leading
)
.animation(.default)
.background(Capsule().stroke(Color.gray, lineWidth: 3))
func setFrame(index: Int, frame: CGRect)
print("Setting frame: \(index): \(frame)")
self.frames[index] = frame
struct FrameKey : PreferenceKey
static var defaultValue: CGRect = .zero
static func reduce(value: inout CGRect, nextValue: () -> CGRect)
value = nextValue()
extension View
func measure() -> some View
self.background(GeometryReader geometry in
Color.clear
.preference(key: FrameKey.self, value: geometry.frame(in: .global))
)
请注意,原来的 .background
调用已被删除并替换为 .measure()
和 .onPreferenceChange
-- 查找 //<-- Here
注释的位置。
除了 PreferenceKey 和 View 扩展之外,没有其他任何改变。
【讨论】:
以上是关于自定义分段控制器 SwiftUI 框架问题的主要内容,如果未能解决你的问题,请参考以下文章