提问人:Tabingtons 提问时间:11/16/2023 最后编辑:Benzy NeezTabingtons 更新时间:11/16/2023 访问量:35
SwiftUI 在分段圆上标记每个线段
SwiftUI Label each segment on a segmented circle
问:
这里是新手,并决定使用 Shape 处理一个具有大元素的应用程序。我创建了一个视图,该视图绘制了一个圆的片段并将它们组合到该圆中。我现在尝试向每个片段添加文本以标记它们(因为它们充当按钮)。我尝试过使用简单的尝试,例如 .overlay,以及在论坛上搜索解决方案,例如: 如何使用 SwiftUI 在我的圈子片段中插入 UIImages?
不幸的是,我无法让标签坐在其各自的细分市场之上。我共享的代码删除了所有标记以显示其当前状态的尝试。
这是我绘制分段轮子的代码:
struct EmotionWheelView: View {
// Prepare array of emotions for the selector
let emotions: [Emotion] = Bundle.main.decode("emotions.json")
// Bringing in the tapped and selected emotions to set in the parent
@Binding public var tappedEmotion: String
@Binding public var selectedEmotion: String
@Binding public var selectedSegment: Int?
var selectableEmotions: [Emotion] {
var results: [Emotion] = []
if selectedEmotion.isEmpty {
for emotion in emotions {
if emotion.level == 1 {
results.append(emotion)
}
}
} else {
var baseEmotion: Emotion {
var result: Emotion = emotions[0]
for emotion in emotions {
if emotion.name == selectedEmotion {
result = emotion
}
}
return result
}
for emotion in emotions {
for subID in baseEmotion.subID {
if emotion.id == subID {
results.append(emotion)
}
}
}
}
return results
}
var data: [Double] {
var results: [Double] = []
for _ in selectableEmotions {
results.append(2.0)
}
return results
}
var colors: [Color] {
var results: [Color] = []
var color: Color = .red
for emotion in selectableEmotions {
switch emotion.color {
case "purple":
color = Color.purple
case "pink":
color = Color.pink
case "orange":
color = Color.orange
case "yellow":
color = Color.yellow
case "green":
color = Color.green
case "teal":
color = Color.teal
case "blue":
color = Color.blue
default:
color = Color.black
}
results.append(color)
}
return results
}
var body: some View {
EmotionWheel(selectableEmotions: selectableEmotions, data: data, colors: colors, tappedEmotion: $tappedEmotion, selectedSegment: $selectedSegment)
.frame(maxHeight: 200)
}
}
struct EmotionWheel: View {
var selectableEmotions: [Emotion]
var data: [Double]
var colors: [Color]
@Binding var tappedEmotion: String
@Binding var selectedSegment: Int?
private var totalValue: Double {
data.reduce(0, +)
}
private var startAngles: [Double] {
var angles: [Double] = []
var currentAngle = -Double.pi / 2
for value in data {
angles.append(currentAngle)
currentAngle += value / totalValue * 2 * .pi
}
return angles
}
private var endAngles: [Double] {
var angles: [Double] = []
var currentAngle = -Double.pi / 2
for value in data {
currentAngle += value / totalValue * 2 * .pi
angles.append(currentAngle)
}
return angles
}
var body: some View {
GeometryReader { geo in
ZStack {
ForEach(0..<data.count, id: \.self) { index in
WheelSegment(startAngle: startAngles[index], endAngle: endAngles[index])
.stroke(Color(UIColor.systemBackground), lineWidth: 1)
.fill(colors[index].gradient)
.onTapGesture {
withAnimation {
selectedSegment = index
tappedEmotion = selectableEmotions[index].name
}
}
.scaleEffect(selectedSegment == index ? 1.1 : 1.0)
.animation(.spring(dampingFraction: 0.8), value: selectedSegment)
}
Circle()
.foregroundStyle(Color(UIColor.systemBackground))
.frame(width: geo.size.width / 2)
.overlay {
Text(tappedEmotion)
.fontWeight(.bold)
.foregroundStyle(getTappedColor(tappedEmotion: tappedEmotion))
.padding()
}
}
.padding(40)
}
}
}
struct WheelSegment: Shape {
var startAngle: Double
var endAngle: Double
func path(in rect: CGRect) -> Path {
var path = Path()
let center = CGPoint(x: rect.midX, y: rect.midY)
let radius = min(rect.width, rect.height)
path.move(to: center)
path.addArc(center: center, radius: radius, startAngle: Angle(radians: startAngle), endAngle: Angle(radians: endAngle), clockwise: false)
path.closeSubpath()
return path
}
}
func getTappedColor(tappedEmotion: String) -> Color {
let emotions: [Emotion] = Bundle.main.decode("emotions.json")
var color: Color = .red
var toColor: Emotion {
for emotion in emotions {
if emotion.name == tappedEmotion {
return emotion
}
}
return emotions[0]
}
switch toColor.color {
case "purple":
color = Color.purple
case "pink":
color = Color.pink
case "orange":
color = Color.orange
case "yellow":
color = Color.yellow
case "green":
color = Color.green
case "teal":
color = Color.teal
case "blue":
color = Color.blue
default:
color = Color.black
}
return color
}
结果:
目标的粗略表示(你明白了):
我尝试使用.overlay,我也尝试使用ZStack并制作两个“圆圈”,一个用于显示颜色,另一个用于显示标签。
答:
0赞
Benzy Neez
11/16/2023
#1
我建议你用 a 来显示轮子的段和标签。ZStack
要使标签显示在圆圈周围的位置,但具有水平方向(如屏幕截图所示),您需要按特定顺序应用 3 个转换:
- 负旋转效果,以否定下面文本的旋转
- 偏移量,用于将标签从 的中心移出到沿圆周的某个位置
ZStack
- 正旋转效果,将偏移文本从 .
ZStack
我发现调整您的代码以显示它的工作有点困难,因此这里有一个小的独立示例,仅显示各个位置的标签:
enum Emotion: CaseIterable {
case happy
case angry
case disgusted
case sad
case delighted
case muted
case shocked
var name: String {
"\(self)".capitalized
}
}
struct ContentView: View {
let emotions: [Emotion] = Emotion.allCases
let radius: CGFloat = 100
func angleForIndex(index: Int) -> Double {
let segmentAngle = 360 / Double(emotions.count)
return (Double(index) + 0.5) * segmentAngle
}
var body: some View {
ZStack {
Circle()
.stroke(lineWidth: 10)
.foregroundColor(.blue.opacity(0.3))
.frame(width: radius * 2, height: radius * 2)
ForEach(Array(emotions.enumerated()), id: \.offset) { index, emotion in
Text(emotion.name)
.rotationEffect(.degrees(-angleForIndex(index: index)))
.offset(y: -radius)
.rotationEffect(.degrees(angleForIndex(index: index)))
}
}
}
}
评论
0赞
Tabingtons
11/24/2023
太棒了,谢谢你在这方面的帮助。解决了我的问题,并给了我一些清理原始代码的技巧。奖金!
评论