iOS SwiftUI颜色选择器切换按钮时视图resize问题求助
问题:ColorSelect视图切换颜色选项时出现拉伸变形
我开发的iOS应用支持自定义用户颜色设置,为此制作了ColorSelect视图,包含切换目标颜色(Color 1/Color 2)的头部按钮,以及4x4的可选颜色网格。正常状态下视图显示正常,但点击切换颜色设置时,视图会出现拉伸变形。
相关代码
import SwiftUI struct ColorSelect: View { // 标记当前修改的是第1还是第2个颜色 @State var colorSelection: Int = 1 // 控制视图显示/隐藏的绑定变量 @Binding var windowVisible: Bool // 绑定用户颜色1 @Binding var color1Link: Color // 绑定用户颜色2 @Binding var color2Link: Color // 颜色对应的RGB数值数组,通过convertColor转换为Color对象 let colors = [144012063, // 暗红 255049049, // 红 255092000, // 橙 255195000, // 深橙 255240031, // 黄 170255000, // 深绿 057255020, // 绿 188237250, // 浅蓝 004217255, // 蓝 031081255, // 深蓝 125018255, // 深紫 176038255, // 紫 255016240, // 粉 255189186, // 桃色 255255255, // 白 170170170 // 灰 ] var body: some View { // 临时数组存储颜色绑定(存在问题) var colorSlots = [color1Link, color2Link] // Color 1按钮文本样式 let col1Label = Text("Color 1") .font(.system(.title2, design: .rounded)) .fontWeight(.heavy) .foregroundStyle(.white) // Color 2按钮文本样式 let col2Label = Text("Color 2") .font(.system(.title2, design: .rounded)) .fontWeight(.heavy) .foregroundStyle(.white) ZStack { // 外层白色圆角背景 RoundedRectangle(cornerRadius: 34.0) .foregroundStyle(.white) .padding(10) // 内层黑色圆角背景 RoundedRectangle(cornerRadius: 30.0) .foregroundStyle(.black) .padding(14) VStack { // 颜色切换头部栏 HStack { Spacer() Button { colorSelection = 1 } label: { colorSelection == 1 ? col1Label.underline() : col1Label } Spacer() Text("|") .font(.system(.title2, design: .rounded)) .fontWeight(.heavy) .foregroundStyle(.white) Spacer() Button { colorSelection = 2 } label: { colorSelection == 2 ? col2Label.underline() : col2Label } Spacer() } .padding() // 4x4颜色选择网格 LazyVGrid(columns: [GridItem(spacing: 5), GridItem(spacing: 5), GridItem(spacing: 5), GridItem(spacing: 5)], spacing: 5) { ForEach(colors, id: \.self) { color in let trueColor = convertColor(rgb: color) let buttonView = RoundedRectangle(cornerRadius: 8) .foregroundStyle(trueColor) .shadow(color: trueColor, radius: 7) // 错误:在循环内使用@State,导致状态管理异常 @State var currColor = color == UserDefaults.standard.integer(forKey: "color\(colorSelection)rgb") if(!currColor) { Button { UserDefaults.standard.set(color, forKey: "color\(colorSelection)rgb") colorSlots[colorSelection - 1] = convertColor(rgb: color) } label: { buttonView } .aspectRatio(contentMode: .fit) } else { ZStack { buttonView Image(systemName: "checkmark") .resizable() .foregroundStyle(.white) .padding() } // 缺失:未设置aspectRatio,导致布局尺寸异常 } } } } .padding(20) // 右上角关闭按钮 VStack { HStack { Spacer() Button { windowVisible = false } label: { Image(systemName: "x.circle") .foregroundColor(.white) .shadow(color: .white, radius: 10) } } Spacer() } } .aspectRatio(contentMode: .fit) } // 将自定义RGB数值转换为Color对象 func convertColor(rgb: Int) -> Color { let b = Double(rgb % 1000) / 255 let g = Double((rgb % 1000000) / 1000) / 255 let r = Double((rgb % 1000000000) / 1000000) / 255 return Color(red: r, green: g, blue: b) } } #Preview { ColorSelect(windowVisible: Binding.constant(true), color1Link: Binding.constant(.black), color2Link: Binding.constant(.black)) }
问题根源
- 循环内使用@State变量:在
ForEach循环内部声明@State var currColor是SwiftUI的错误用法,@State只能在视图的顶层声明,循环内的@State会导致状态管理混乱,触发不必要的视图刷新和布局计算错误。 - 部分子视图缺失尺寸约束:选中颜色的ZStack没有设置
.aspectRatio,导致网格内元素尺寸不一致,切换状态时布局重新计算引发拉伸。 - 局部数组
colorSlots的错误使用:在body内声明的局部数组无法正确同步绑定变量的更新,间接导致视图状态不稳定。
修复方案
- 移除循环内的@State:将
currColor改为直接计算的常量,不要用@State:
let currColor = color == UserDefaults.standard.integer(forKey: "color\(colorSelection)rgb")
- 统一子视图尺寸约束:给所有网格子视图(按钮和带对勾的ZStack)都添加
.aspectRatio(1, contentMode: .fit),确保每个颜色块都是正方形:
// 按钮部分 Button { // ...点击逻辑 } label: { buttonView } .aspectRatio(1, contentMode: .fit) // 选中状态的ZStack部分 ZStack { buttonView Image(systemName: "checkmark") .resizable() .foregroundStyle(.white) .padding() } .aspectRatio(1, contentMode: .fit)
- 优化绑定变量更新逻辑:去掉局部数组
colorSlots,直接修改绑定变量:
Button { UserDefaults.standard.set(color, forKey: "color\(colorSelection)rgb") if colorSelection == 1 { color1Link = convertColor(rgb: color) } else { color2Link = convertColor(rgb: color) } } label: { buttonView }
修复后完整代码
import SwiftUI struct ColorSelect: View { @State var colorSelection: Int = 1 @Binding var windowVisible: Bool @Binding var color1Link: Color @Binding var color2Link: Color let colors = [144012063, 255049049, 255092000, 255195000, 255240031, 170255000, 057255020, 188237250, 004217255, 031081255, 125018255, 176038255, 255016240, 255189186, 255255255, 170170170] var body: some View { let col1Label = Text("Color 1") .font(.system(.title2, design: .rounded)) .fontWeight(.heavy) .foregroundStyle(.white) let col2Label = Text("Color 2") .font(.system(.title2, design: .rounded)) .fontWeight(.heavy) .foregroundStyle(.white) ZStack { RoundedRectangle(cornerRadius: 34.0) .foregroundStyle(.white) .padding(10) RoundedRectangle(cornerRadius: 30.0) .foregroundStyle(.black) .padding(14) VStack { HStack { Spacer() Button { colorSelection = 1 } label: { colorSelection == 1 ? col1Label.underline() : col1Label } Spacer() Text("|") .font(.system(.title2, design: .rounded)) .fontWeight(.heavy) .foregroundStyle(.white) Spacer() Button { colorSelection = 2 } label: { colorSelection == 2 ? col2Label.underline() : col2Label } Spacer() } .padding() LazyVGrid(columns: Array(repeating: GridItem(spacing: 5), count: 4), spacing: 5) { ForEach(colors, id: \.self) { color in let trueColor = convertColor(rgb: color) let buttonView = RoundedRectangle(cornerRadius: 8) .foregroundStyle(trueColor) .shadow(color: trueColor, radius: 7) let currColor = color == UserDefaults.standard.integer(forKey: "color\(colorSelection)rgb") if !currColor { Button { UserDefaults.standard.set(color, forKey: "color\(colorSelection)rgb") colorSelection == 1 ? (color1Link = trueColor) : (color2Link = trueColor) } label: { buttonView } .aspectRatio(1, contentMode: .fit) } else { ZStack { buttonView Image(systemName: "checkmark") .resizable() .foregroundStyle(.white) .padding() } .aspectRatio(1, contentMode: .fit) } } } } .padding(20) VStack { HStack { Spacer() Button { windowVisible = false } label: { Image(systemName: "x.circle") .foregroundColor(.white) .shadow(color: .white, radius: 10) } } Spacer() } } .aspectRatio(contentMode: .fit) } func convertColor(rgb: Int) -> Color { let b = Double(rgb % 1000) / 255 let g = Double((rgb % 1000000) / 1000) / 255 let r = Double((rgb % 1000000000) / 1000000) / 255 return Color(red: r, green: g, blue: b) } } #Preview { ColorSelect(windowVisible: Binding.constant(true), color1Link: Binding.constant(.black), color2Link: Binding.constant(.black)) }
内容的提问来源于stack exchange,提问作者Alec Ruedi
相关产品推荐
相关产品推荐

