Как добавить круглый наконечник на обрезной круг в SwiftUI

#swift #swiftui

#swift #swiftui

Вопрос:

Я хочу создать индикатор выполнения (круг) с большим круглым наконечником, который будет следовать за прогрессом. Не могли бы вы подсказать мне, есть ли какой-нибудь стандартный способ сделать это? Или для понимания положения требуется дополнительный круг и некоторые вычисления?

 struct Loader: View {
    
    var percent: CGFloat = 0
    var colors: [Color] = [
        Color(red: 0.949, green: 0.9098, blue: 0.8706),
        Color(red: 0.9569, green: 0.5882, blue: 0.2196)
    ]
     
    var body: some View {
        ZStack{
            Circle()
                .fill(Color.white)
                .frame(width: 300, height: 300, alignment: .center)
                .overlay(
                    Circle()
                        .trim(from: 0, to: percent * 0.01)
                        .stroke(style: StrokeStyle(lineWidth: 22, lineCap: .butt, lineJoin: .miter, miterLimit: 10))
                        .fill(AngularGradient(gradient: .init(colors: colors), center: .center, startAngle: .zero, endAngle: .init(degrees: 360)))
                        .rotationEffect(.degrees(-90))
                ).animation(.spring(response: 1.0, dampingFraction: 1.0, blendDuration: 1.0))
            
            Text(String(format: "%.1f", percent)   " %").font(.system(size: 20)).fontWeight(.heavy)
        }
    }
}
 

Что я хочу сделать:

введите описание изображения здесь

Что я уже сделал:

введите описание изображения здесь

Ответ №1:

Вот потенциальное решение. Я добавил наложение поверх загрузчика, я увеличил его высоту до полного размера большого круга, а затем просто повернул наложение с завершением. Таким образом, они анимируются одновременно.

 import SwiftUI

struct CircleView: View {
    
    @State var percent: CGFloat = 0
    
    var body: some View {
        Loader(percent: $percent)
            .onAppear {
                DispatchQueue.main.asyncAfter(deadline: .now()   2.0) {
                    withAnimation(Animation.linear(duration: 1.0)) {
                        percent = 20
                    }
                }
                DispatchQueue.main.asyncAfter(deadline: .now()   3.0) {
                    withAnimation(Animation.linear(duration: 2.0)) {
                        percent = 55
                    }
                }
                DispatchQueue.main.asyncAfter(deadline: .now()   5.0) {
                    withAnimation(Animation.linear(duration: 4.0)) {
                        percent = 100
                    }
                }
            }
    }
}

struct CircleView_Previews: PreviewProvider {
    static var previews: some View {
        CircleView()
    }
}


struct Loader: View {
    
    @Binding var percent: CGFloat
    var colors: [Color] = [
        Color(red: 0.949, green: 0.9098, blue: 0.8706),
        Color(red: 0.9569, green: 0.5882, blue: 0.2196)
    ]
    
    let circleHeight: CGFloat = 300
     
    var body: some View {
        let pinHeight = circleHeight * 0.1
        let completion = percent * 0.01
        Circle()
            .trim(from: 0, to: completion)
            .stroke(style: StrokeStyle(lineWidth: 22, lineCap: .butt, lineJoin: .miter, miterLimit: 10))
            .fill(AngularGradient(gradient: .init(colors: colors), center: .center, startAngle: .zero, endAngle: .init(degrees: 360)))
            .rotationEffect(.degrees(-90))
            .frame(width: circleHeight, height: circleHeight)
            .overlay(
                Text(String(format: "%.1f", percent)   " %").font(.system(size: 20)).fontWeight(.heavy)
                    .animation(nil)
            )
            .overlay(
                Circle()
                    .frame(width: pinHeight, height: pinHeight)
                    .offset(y: -pinHeight / 2)
                    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
                    .rotationEffect(Angle(degrees: 360 * Double(completion)))
            )
    }
}
 

Комментарии:

1. Спасибо. Я не уверен в интерфейсе и SwiftUI, но ваше предложение кажется довольно простым в реализации.

Ответ №2:

Вы можете изменить тип обводки, изменив модификатор lineCap в strokeStyle следующим образом:

    .trim(from: 0, to: 0.75)
   .stroke(Color.black, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round))
 

демонстрация