SwiftUI:如何利用GeometryReader让嵌套环形视图适配任意父视图尺寸
嵌套环形Gauge适配父视图缩放问题
我写的这款嵌套环形UI能正常运行,但不知道怎么改代码,让它在父视图极小或极大时都能自适应缩放。
未设置frame时的效果:
设置frame高度为100后的效果:
原代码如下:
import SwiftUI struct CustomGaugeStyleView: View { @State private var innerRingFill = 6.5 var body: some View { Gauge(value: innerRingFill, in: 0...10) { Image(systemName: "gauge.medium") .font(.system(size: 50.0)) } currentValueLabel: { Text("\(innerRingFill.formatted(.number))") } .gaugeStyle(twoRingGaugeStyle(outerRingMin: 5.5, outerRingMax: 7.5)) } } struct CustomGaugeStyleView_Previews: PreviewProvider { static var previews: some View { CustomGaugeStyleView() } } struct twoRingGaugeStyle: GaugeStyle { var outerRingMin: Double var outerRingMax: Double func makeBody(configuration: Configuration) -> some View { GeometryReader { geometry in ZStack { Circle() .stroke(Color(.lightGray).opacity(0.2), style: StrokeStyle(lineWidth: 20)) .frame(height: geometry.size.height * 0.70) Circle() .trim(from: 0, to: 0.75 * configuration.value) .stroke(Color.orange.gradient, style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round)) .rotationEffect(.degrees(270)) .frame(height: geometry.size.height * 0.70) Circle() .trim(from: outerRingMin / 10, to: outerRingMax / 10) .stroke(Color.green.gradient, style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round)) .rotationEffect(.degrees(270)) .frame(height: geometry.size.height * 0.82) } .padding() } .aspectRatio(contentMode: .fit) } }
解决思路与修改后的代码
核心问题是原代码用了固定线宽(20)和固定比例系数,极端尺寸下会导致环形变形。改成动态计算所有尺寸,基于父视图的最小边维持比例,就能实现自适应缩放:
import SwiftUI struct CustomGaugeStyleView: View { @State private var innerRingFill = 6.5 var body: some View { Gauge(value: innerRingFill, in: 0...10) { Image(systemName: "gauge.medium") // 图标自适应尺寸 .font(.system(size: min(UIScreen.main.bounds.width, UIScreen.main.bounds.height) * 0.1)) } currentValueLabel: { Text("\(innerRingFill.formatted(.number))") .font(.title) .fontWeight(.bold) } .gaugeStyle(TwoRingGaugeStyle(outerRingMin: 5.5, outerRingMax: 7.5)) // 允许Gauge填充父视图空间 .frame(maxWidth: .infinity, maxHeight: .infinity) } } struct CustomGaugeStyleView_Previews: PreviewProvider { static var previews: some View { Group { // 预览极小尺寸 CustomGaugeStyleView() .frame(width: 50, height: 50) // 预览极大尺寸 CustomGaugeStyleView() .frame(width: 500, height: 500) // 预览默认尺寸 CustomGaugeStyleView() } } } // 遵循Swift命名规范,首字母大写 struct TwoRingGaugeStyle: GaugeStyle { var outerRingMin: Double var outerRingMax: Double func makeBody(configuration: Configuration) -> some View { GeometryReader { geometry in // 取父视图最小边,保证环形始终是正圆 let minDimension = min(geometry.size.width, geometry.size.height) // 动态计算线宽,占最小边的1/15左右,适配不同尺寸 let lineWidth = minDimension * 0.067 // 维持原有环形比例,基于动态尺寸计算 let innerRingDiameter = minDimension * 0.7 let outerRingDiameter = minDimension * 0.82 ZStack { // 内圈背景 Circle() .stroke(Color(.lightGray).opacity(0.2), style: StrokeStyle(lineWidth: lineWidth)) .frame(width: innerRingDiameter, height: innerRingDiameter) // 内圈填充环 Circle() .trim(from: 0, to: 0.75 * configuration.value) .stroke(Color.orange.gradient, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round)) .rotationEffect(.degrees(270)) .frame(width: innerRingDiameter, height: innerRingDiameter) // 外圈绿色指示环 Circle() .trim(from: outerRingMin / 10, to: outerRingMax / 10) .stroke(Color.green.gradient, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round)) .rotationEffect(.degrees(270)) .frame(width: outerRingDiameter, height: outerRingDiameter) // 把Gauge的标签和数值放到环形中心 VStack { configuration.label configuration.currentValueLabel } } // 动态内边距,适配不同尺寸 .padding(minDimension * 0.05) } // 强制1:1比例,确保环形不会被拉伸 .aspectRatio(1, contentMode: .fit) } }
关键修改点
- 动态线宽:不再用固定值,基于父视图最小边比例计算,保证粗细协调
- 动态环形尺寸:内圈、外圈直径都基于父视图最小边,维持原有视觉比例
- 强制正圆:用
aspectRatio(1, contentMode: .fit)确保环形始终是圆形 - 自适应文本/图标:图标和数值大小随尺寸动态调整,避免截断或过小
- 规范命名:调整样式结构体名称符合Swift大驼峰规则
内容的提问来源于stack exchange,提问作者GarySabo
相关产品推荐
相关产品推荐

