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

SwiftUI自定义扇形可点击按钮实现问题求助

帮你解决扇形按钮的编译错误 & 变量传递问题

嘿,我来帮你拆解这段代码里的问题,一步步解决你的需求~

首先,你的代码里有几个关键的编译错误和SwiftUI/UIKit混用的问题,我逐个给你说明:

一、核心错误点分析

  1. SwiftUI View的body不能有赋值操作
    你在ContentView的body里写了NewButtons = CustomShapeButton(),但SwiftUI的body是一个纯函数式的描述结构,不能在这里修改属性或者执行赋值这类副作用操作,这会直接导致编译失败。

  2. UIKit组件不能直接在SwiftUI里使用
    CustomShapeButton是UIButton的子类,属于UIKit组件,而SwiftUI需要通过UIViewRepresentable协议来桥接UIKit视图,直接放在SwiftUI的ZStack里是不允许的。

  3. 变量作用域错误
    你在CustomShapeButton里直接使用了i和j,但这两个变量是ContentView里ForEach的循环变量,根本不在CustomShapeButton的作用域内,编译器找不到这两个变量,自然会报错。

  4. 未初始化的属性
    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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:16:25