You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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))
}

问题根源

  1. 循环内使用@State变量:在ForEach循环内部声明@State var currColor是SwiftUI的错误用法,@State只能在视图的顶层声明,循环内的@State会导致状态管理混乱,触发不必要的视图刷新和布局计算错误。
  2. 部分子视图缺失尺寸约束:选中颜色的ZStack没有设置.aspectRatio,导致网格内元素尺寸不一致,切换状态时布局重新计算引发拉伸。
  3. 局部数组colorSlots的错误使用:在body内声明的局部数组无法正确同步绑定变量的更新,间接导致视图状态不稳定。

修复方案

  1. 移除循环内的@State:将currColor改为直接计算的常量,不要用@State:
let currColor = color == UserDefaults.standard.integer(forKey: "color\(colorSelection)rgb")
  1. 统一子视图尺寸约束:给所有网格子视图(按钮和带对勾的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)
  1. 优化绑定变量更新逻辑:去掉局部数组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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 22:47:03