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

SwiftUI中实现按钮矩阵点击切换填充颜色的问题

SwiftUI 齿科图表组件修复:实现五按钮矩阵及颜色切换

问题描述

需要在SwiftUI中构建包含顶部、右侧、左侧、底部、中心五个按钮的矩阵结构,点击任意按钮可切换其填充颜色。当前编写的代码(含OdontogramComponentView、View扩展及EdgeBorder结构体)未达到预期效果,具体信息如下:

预期效果

预期效果

现有代码

OdontogramComponentView 主视图

import SwiftUI

struct OdontogramComponentView: View {
    
    @State private var leftButtonColor = Color.blue
    @State private var leftBorderColor = Color.black
    @State private var rightButtonColor = Color.red
    @State private var rightBorderColor = Color.black
    @State private var leftButtonPressed = false
    @State private var rightButtonPressed = false
    
    var body: some View {
        ZStack {
            HStack(spacing: 0) {
                Button(action: {
                    self.leftButtonPressed = true
                    self.rightButtonPressed = false
                    self.changeLeftButtonColor()
                }) {
                    Text("")
                        .foregroundColor(.white)
                        .frame(width: 40, height: 80)
                        .background(leftButtonColor)
                        .border(width: 2, edges: [.top, .leading, .bottom], color: leftBorderColor)
                }
                
                Button(action: {
                    self.leftButtonPressed = false
                    self.rightButtonPressed = true
                    self.changeRightButtonColor()
                }) {
                    Text("")
                        .foregroundColor(.white)
                        .frame(width: 40, height: 80)
                        .background(rightButtonColor)
                        .border(width: 2, edges: [.top, .trailing, .bottom], color: rightBorderColor)
                }
            }
            
            Rectangle()
                .fill(Color.black)
                .frame(width: 110, height: 2)
                .offset(x: 0, y: 0)
                .rotationEffect(.degrees(45))
            
            Rectangle()
                .fill(Color.black)
                .frame(width: 2, height: 110)
                .offset(x: 0, y: 0)
                .rotationEffect(.degrees(45))
            
            Button(action: {
                self.leftButtonPressed = false
                self.rightButtonPressed = false
                self.changeLeftButtonColor()
                self.changeRightButtonColor()
            }) {
                Image(systemName: "")
                    .foregroundColor(.white)
                    .font(.system(size: 20, weight: .bold))
                    .frame(width: 40, height: 40)
                    .background(leftButtonColor)
                    .border(leftBorderColor, width: 2)
            }
        }
        .frame(width: 120, height: 120)
    }
    
    private func changeLeftButtonColor() {
        if leftButtonPressed {
            switch leftButtonColor {
            case .blue:
                leftButtonColor = .red
                leftBorderColor = .black
            case .red:
                leftButtonColor = .white
                leftBorderColor = .black
            case .white:
                leftButtonColor = .blue
                leftBorderColor = .black
            default:
                break
            }
        }
    }
    
    private func changeRightButtonColor() {
        if rightButtonPressed {
            switch rightButtonColor {
            case .blue:
                rightButtonColor = .red
                rightBorderColor = .black
            case .red:
                rightButtonColor = .white
                rightBorderColor = .black
            case .white:
                rightButtonColor = .blue
                rightBorderColor = .black
            default:
                break
            }
        }
    }
}

View 扩展代码

import SwiftUI

extension View {
    
    func border(width: CGFloat, edges: [Edge], color: Color) -> some View {
        overlay(EdgeBorder(width: width, edges: edges).foregroundColor(color))
    }
}

EdgeBorder 结构体代码

import SwiftUI

struct EdgeBorder: Shape {
    var width: CGFloat
    var edges: [Edge]

    func path(in rect: CGRect) -> Path {
        edges.map { edge -> Path in
            switch edge {
            case .top: return Path(.init(x: rect.minX, y: rect.minY, width: rect.width, height: width))
            case .bottom: return Path(.init(x: rect.minX, y: rect.maxY - width, width: rect.width, height: width))
            case .leading: return Path(.init(x: rect.minX, y: rect.minY, width: width, height: rect.height))
            case .trailing: return Path(.init(x: rect.maxX - width, y: rect.minY, width: width, height: rect.height))
            }
        }.reduce(into: Path()) { $0.addPath($1) }
    }
}

当前效果

当前效果


修复方案

问题分析

  1. 原代码仅实现左右两个按钮,缺少顶部、底部按钮的布局与逻辑
  2. 中心按钮背景色错误绑定到leftButtonColor,未独立管理状态
  3. 颜色切换逻辑依赖pressed标记,点击其他按钮会重置状态导致切换失效
  4. 旋转矩形分割线与预期的十字分割不符

完整修复代码

1. 重构状态与布局

用枚举统一颜色切换逻辑,为五个按钮创建独立状态,重构布局匹配预期结构:

import SwiftUI

// 统一管理按钮颜色状态
enum ButtonColorState: CaseIterable {
    case blue, red, white
    
    var color: Color {
        switch self {
        case .blue: return .blue
        case .red: return .red
        case .white: return .white
        }
    }
    
    // 切换到下一个颜色状态
    func next() -> ButtonColorState {
        let allCases = ButtonColorState.allCases
        guard let currentIndex = allCases.firstIndex(of: self) else { return self }
        let nextIndex = (currentIndex + 1) % allCases.count
        return allCases[nextIndex]
    }
}

struct OdontogramComponentView: View {
    // 五个按钮的独立状态
    @State private var topState: ButtonColorState = .blue
    @State private var leftState: ButtonColorState = .blue
    @State private var centerState: ButtonColorState = .blue
    @State private var rightState: ButtonColorState = .red
    @State private var bottomState: ButtonColorState = .blue
    
    var body: some View {
        ZStack {
            // 嵌套布局实现五按钮矩阵
            VStack(spacing: 0) {
                // 顶部按钮
                Button(action: { topState = topState.next() }) {
                    Text("")
                        .frame(width: 80, height: 40)
                        .background(topState.color)
                        .border(width: 2, edges: [.top, .leading, .trailing], color: .black)
                }
                
                HStack(spacing: 0) {
                    // 左侧按钮
                    Button(action: { leftState = leftState.next() }) {
                        Text("")
                            .frame(width: 40, height: 40)
                            .background(leftState.color)
                            .border(width: 2, edges: [.leading, .bottom], color: .black)
                    }
                    
                    // 中心按钮
                    Button(action: { centerState = centerState.next() }) {
                        Text("")
                            .frame(width: 40, height: 40)
                            .background(centerState.color)
                            .border(.black, width: 2)
                    }
                    
                    // 右侧按钮
                    Button(action: { rightState = rightState.next() }) {
                        Text("")
                            .frame(width: 40, height: 40)
                            .background(rightState.color)
                            .border(width: 2, edges: [.trailing, .bottom], color: .black)
                    }
                }
                
                // 底部按钮
                Button(action: { bottomState = bottomState.next() }) {
                    Text("")
                        .frame(width: 80, height: 40)
                        .background(bottomState.color)
                        .border(width: 2, edges: [.leading, .trailing, .bottom], color: .black)
                }
            }
            
            // 十字分割线
            VStack {
                Rectangle()
                    .fill(.black)
                    .frame(width: 2, height: 120)
            }
            HStack {
                Rectangle()
                    .fill(.black)
                    .frame(width: 120, height: 2)
            }
        }
        .frame(width: 120, height: 120)
    }
}

2. 复用原有扩展与结构体

无需修改之前的View扩展和EdgeBorder结构体,直接复用即可。

修复说明

  • 用ButtonColorState枚举封装颜色切换逻辑,减少重复代码,提升可维护性
  • 为五个按钮设置独立状态变量,点击时仅切换自身颜色,互不干扰
  • 采用VStack嵌套HStack的布局,完美匹配预期的五按钮矩阵结构
  • 替换旋转矩形为垂直+水平的十字分割线,还原预期视觉效果
  • 调整每个按钮的边框设置,保证整体边框连贯统一

内容的提问来源于stack exchange,提问作者jdanvz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 15:27:52