SwiftUI自定义扇形可点击按钮实现问题求助
帮你解决扇形按钮的编译错误 & 变量传递问题
嘿,我来帮你拆解这段代码里的问题,一步步解决你的需求~
首先,你的代码里有几个关键的编译错误和SwiftUI/UIKit混用的问题,我逐个给你说明:
一、核心错误点分析
SwiftUI View的body不能有赋值操作
你在ContentView的body里写了NewButtons = CustomShapeButton(),但SwiftUI的body是一个纯函数式的描述结构,不能在这里修改属性或者执行赋值这类副作用操作,这会直接导致编译失败。UIKit组件不能直接在SwiftUI里使用
CustomShapeButton是UIButton的子类,属于UIKit组件,而SwiftUI需要通过UIViewRepresentable协议来桥接UIKit视图,直接放在SwiftUI的ZStack里是不允许的。变量作用域错误
你在CustomShapeButton里直接使用了i和j,但这两个变量是ContentView里ForEach的循环变量,根本不在CustomShapeButton的作用域内,编译器找不到这两个变量,自然会报错。未初始化的属性
ContentView里的NewButtons属性没有初始化,而且这个属性本身在SwiftUI的结构里是多余的,完全不需要。
二、修正后的完整代码
我把代码重构了一下,解决了所有问题,同时实现了把i和j传递给每个扇形按钮的需求:
1. 先修改CustomShapeButton,支持接收i和j参数
import UIKit class CustomShapeButton: UIButton { private let i: Int private let j: Int // 添加初始化方法,接收i和j init(i: Int, j: Int) { self.i = i self.j = j super.init(frame: .zero) self.backgroundColor = .clear // 设为透明,只让扇形区域响应点击 } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } lazy var pantsShapeBezierPath: UIBezierPath = { let path = UIBezierPath() // 使用self.j计算半径 var r = CGFloat(50.0 + (CGFloat(self.j) - 1.0) * 50.0) let center_x = CGFloat(100.0) // 调整为按钮frame的中心(因为按钮frame是200x200) let center_y = CGFloat(100.0) // 使用self.i计算扇形起始角度 let arc_start = CGFloat((45.0 + (CGFloat(self.i) - 1.0) * 90.0)) * CGFloat(Double.pi) / 180.0 let arc_length = CGFloat(90.0 * CGFloat(Double.pi) / 180.0) let arc_width = CGFloat(45.0) let line0Target_x = center_x + r * CGFloat(cos(Double(arc_start))) let line0Target_y = center_y + r * CGFloat(sin(Double(arc_start))) let line1Target_x = center_x + (r + arc_width) * CGFloat(cos(Double(arc_start + arc_length))) let line1Target_y = center_y + (r + arc_width) * CGFloat(sin(Double(arc_start + arc_length))) path.move(to: CGPoint(x: line0Target_x, y: line0Target_y)) path.addArc(center: CGPoint(x: center_x, y: center_y), radius: r, startAngle: Angle(radians: Double(arc_start)), endAngle: Angle(radians: Double(arc_start + arc_length)), clockwise: false) path.addLine(to: CGPoint(x: line1Target_x, y: line1Target_y)) path.addArc(center: CGPoint(x: center_x, y: center_y), radius: (r + arc_width), startAngle: Angle(radians: Double(arc_start + arc_length)), endAngle: Angle(radians: Double(arc_start)), clockwise: true) path.addLine(to: CGPoint(x: line0Target_x, y: line0Target_y)) path.close() return path }() override func draw(_ rect: CGRect) { super.draw(rect) UIColor.red.setFill() pantsShapeBezierPath.fill() } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if pantsShapeBezierPath.contains(point) { return self } else { return nil } } }
2. 创建UIViewRepresentable桥接器,让SwiftUI能使用这个按钮
import SwiftUI struct CustomShapeButtonView: UIViewRepresentable { let i: Int let j: Int // 回调按钮点击事件(如果需要的话可以添加) var onTap: (() -> Void)? func makeUIView(context: Context) -> CustomShapeButton { let button = CustomShapeButton(i: i, j: j) button.addTarget(context.coordinator, action: #selector(Coordinator.handleTap), for: .touchUpInside) return button } func updateUIView(_ uiView: CustomShapeButton, context: Context) {} // 协调器处理点击事件 class Coordinator: NSObject { var parent: CustomShapeButtonView init(parent: CustomShapeButtonView) { self.parent = parent } @objc func handleTap() { parent.onTap?() } } func makeCoordinator() -> Coordinator { Coordinator(parent: self) } }
3. 修改ContentView,正确创建每个扇形按钮
struct ContentView: View { static let segmentCount = 4 static let circleCount = 4 var body: some View { ZStack { ForEach(1..<ContentView.circleCount, id: \.self){ j in ForEach(1..<ContentView.segmentCount, id: \.self){ i in CustomShapeButtonView(i: i, j: j) { // 这里可以添加按钮点击后的操作 print("按钮i:\(i), j:\(j)被点击了") } .frame(width: 200, height: 200) // 给按钮设置明确的frame } } } } }
三、关于i和j的传递问题
当然可以传递!我在上面的代码里已经实现了:
- 给
CustomShapeButton添加了带i和j参数的初始化方法 - 在
CustomShapeButtonView里接收这两个参数,传递给底层的CustomShapeButton - 在
ContentView的ForEach循环里,把当前的i和j值传给每个CustomShapeButtonView
这样每个扇形按钮都能拿到自己对应的i和j,用来计算不同的扇形位置和大小。
内容的提问来源于stack exchange,提问作者Bathan
相关产品推荐
相关产品推荐

