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

SwiftUI:如何利用GeometryReader让嵌套环形视图适配任意父视图尺寸

嵌套环形Gauge适配父视图缩放问题

我写的这款嵌套环形UI能正常运行,但不知道怎么改代码,让它在父视图极小或极大时都能自适应缩放。

未设置frame时的效果:
未设置frame尺寸

设置frame高度为100后的效果:
设置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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 04:17:47