본문 바로가기

프로젝트/FastingTimer

01. 인터페이스 디자인 #1

인터페이스 디자인 #1
ProgressRing, Timer


ProgressRing

ProgressRing은 총 진행상황을 표시할 PlaceHolder와 현재 진행 상황을 표시할 진행링 두 개의 View의 조합이다.
같은 좌표에 겹쳐 표시돼야 하므로 ZStack으로 구현한다.

struct ProgressRing: View {
    @State var progress = 0.0

    var body: some View {

우선 진행 상태를 나타낼 변수를 하나 정의한다.

var body: some View {
        ZStack {
            // MARK: Placholder Ring
            Circle()
                .stroke(lineWidth: 20)
                .foregroundColor(Color(UIColor.systemGray))
                .opacity(0.2)

PlaceHolder에 해당하는 원을 만든다.
두께 20의 테두리를 갖도록 stroke를 사용하고,
Dark mode, Light mode 두 가지 Scheme에 대응할 수 있도록 UIColor의 systemGray를 사용했다.

var body: some View {
        ZStack {
            // MARK: Placholder Ring
            Circle()
                .stroke(lineWidth: 20)
                .foregroundColor(Color(UIColor.systemGray))
                .opacity(0.2)

            // MARK: Color Ring
            Circle()
                .trim(from: 0.0, to: min(progress, 1.0))
                .stroke(AngularGradient(colors: [Color(#colorLiteral(red: 0.8567120433, green: 0.5915268064, blue: 1, alpha: 1)), Color(#colorLiteral(red: 1, green: 0.5821906924, blue: 0.729834497, alpha: 1)), Color(#colorLiteral(red: 0.7210034728, green: 0.9851679206, blue: 0.5617409945, alpha: 1)), Color(#colorLiteral(red: 0.5453777909, green: 0.9893621802, blue: 0.850672543, alpha: 1)), Color(#colorLiteral(red: 0.8567120433, green: 0.5915268064, blue: 1, alpha: 1))], center: .center), style: StrokeStyle(lineWidth: 15.0, lineCap: .round, lineJoin: .round))

진행링은 같은 방식으로 색을 입혀 구현한다.

trim(to:from:)

 

Apple Developer Documentation

 

developer.apple.com

trim은 전체 모양의 일부분을 잘라내 표시하게 하는 modifier다.
0.0부터 진행 상황만큼 잘라내 진생 상태를 표시하게 된다.

angulargradient(_:center:startangle:endangle:) 

 

Apple Developer Documentation

 

developer.apple.com

angularGradient는 중심점을 기준으로 일정한 각도로 변화는 그라데이션을 구현한다.
startangle과 endangle은 필수 파라미터가 아니기 때문에 생략하고,
첫번째 파라미터에는 그라데이션을 표현할 색상의 배열을 전달하며, center로는 중심점을 지정한다.

이때 AngularGradient가 중심점을 기준으로 회전하며 색을 표시하기 때문에 배열의 시작과 끝이 같은 색이어야 오른쪽과 같이 자연스럽게 이어지는 그라데이션을 얻을 수 있다.

var body: some View {
    ZStack {
        .
        .
        .
    }
    .frame(width: 250, height: 250)
    .padding()
    .onAppear {
        progress = 0.5
    }
}

지금 상태로는 ProgressRing의 크기가 화면을 가득 채우게 된다.
적절한 크기로 표시될 수 있도록 frame modifier로 크기를 지정한다.
진행링의 변화를 확인 할 수 있도록 ZStack이 화면에 표시괴는 시점에 progress를 변경한다.
onAppear modifier를 사용한다.

조금 더 안정적인 비율로 진행 바가 표시된다.

지금 상태로는 진행링이 오른쪽부터 시작하기 때문에 위쪽에서 시작하도록 원을 살짝 회전시켜 준다.

Circle()
    .trim(from: 0.0, to: min(progress, 1.0))
    .stroke(AngularGradient(colors: [Color(#colorLiteral(red: 0.8567120433, green: 0.5915268064, blue: 1, alpha: 1)), Color(#colorLiteral(red: 1, green: 0.5821906924, blue: 0.729834497, alpha: 1)), Color(#colorLiteral(red: 0.7210034728, green: 0.9851679206, blue: 0.5617409945, alpha: 1)), Color(#colorLiteral(red: 0.5453777909, green: 0.9893621802, blue: 0.850672543, alpha: 1)), Color(#colorLiteral(red: 0.8567120433, green: 0.5915268064, blue: 1, alpha: 1))], center: .center), style: StrokeStyle(lineWidth: 15.0, lineCap: .round, lineJoin: .round))
    .rotationEffect(Angle(degrees: 270))

rotationEffect modifier는 파라미터로 전달된 각도만큼 View를 회전시킨다.

Circle()
    .trim(from: 0.0, to: min(progress, 1.0))
    .stroke(AngularGradient(colors: [Color(#colorLiteral(red: 0.8567120433, green: 0.5915268064, blue: 1, alpha: 1)), Color(#colorLiteral(red: 1, green: 0.5821906924, blue: 0.729834497, alpha: 1)), Color(#colorLiteral(red: 0.7210034728, green: 0.9851679206, blue: 0.5617409945, alpha: 1)), Color(#colorLiteral(red: 0.5453777909, green: 0.9893621802, blue: 0.850672543, alpha: 1)), Color(#colorLiteral(red: 0.8567120433, green: 0.5915268064, blue: 1, alpha: 1))], center: .center), style: StrokeStyle(lineWidth: 15.0, lineCap: .round, lineJoin: .round))
    .rotationEffect(Angle(degrees: 270))
    .animation(.easeInOut(duration: 1.0), value: progress)

animation modifier를 추가해 progress가 변할 때 마다 easeInOut animation을 적용한다.

 

Timer

ProgressRing의 가운데에는 진행 시간과 남은 시간을 표시할 예정이다.
본격적인 기능은 조금 뒤에 구현하도록 하고, 어떻게 표시될 지 위치를 잡아 보자.

var body: some View {
    ZStack {
        // MARK: Placholder Ring
        Circle()
            .
            .
            .

        // MARK: Color Ring
        Circle()
            .
            .
            .

        VStack(spacing: 30) {
            VStack(spacing: 5) {
                Text("Elapsed Time")
                    .opacity(0.7)

                Text("0:00")
                    .font(.title)
                    .fontWeight(.bold)
            }

            VStack(spacing: 5) {
                Text("Remaing Time")
                    .opacity(0.7)

                Text("0:00")
                    .font(.title2)
                    .fontWeight(.bold)
            }
        }
    }
    .frame(width: 250, height: 250)
    .padding()
    .onAppear {
        progress = 0.5
    }
}

VStack의 spacing 파라미터를 30으로 지정해 포함하는 View들의 간격을 조정한다.
각각의 Timer들은 제목과 시간을 표시하며 간격은 5다.

.
.
.
VStack(spacing: 30) {
    VStack(spacing: 5) {
        Text("Elapsed Time")
            .opacity(0.7)

        Text("0:00")
            .font(.title)
            .fontWeight(.bold)
    }
    .padding(.top)

    VStack(spacing: 5) {
        Text("Remaing Time")
            .opacity(0.7)

        Text("0:00")
            .font(.title2)
            .fontWeight(.bold)
    }
}
.
.
.

조금 윗쪽으로 치우친 느낌이 있기 때문에 상단에 padding을 추가해 오른쪽 사진과 같이 위치를 조정했다.

더보기

Source

//
//  ProgressRing.swift
//  FastingTimer_B
//
//  Created by Martin.Q on 2023/02/15.
//

import SwiftUI

struct ProgressRing: View {
    @State var progress = 0.0

    var body: some View {
        ZStack {
            // MARK: Place Holder
            Circle()
                .stroke(lineWidth: 20)
                .foregroundColor(Color(UIColor.systemGray))
                .opacity(0.2)

            // MARK: Process Ring
            Circle()
                .trim(from: 0, to: min(progress, 1.0))
                .stroke(AngularGradient(colors: [Color(#colorLiteral(red: 0.8567120433, green: 0.5915268064, blue: 1, alpha: 1)), Color(#colorLiteral(red: 1, green: 0.5821906924, blue: 0.729834497, alpha: 1)), Color(#colorLiteral(red: 0.7210034728, green: 0.9851679206, blue: 0.5617409945, alpha: 1)), Color(#colorLiteral(red: 0.5453777909, green: 0.9893621802, blue: 0.850672543, alpha: 1)), Color(#colorLiteral(red: 0.8567120433, green: 0.5915268064, blue: 1, alpha: 1))], center: .center), style: StrokeStyle(lineWidth: 15, lineCap: .round, lineJoin: .round))
                .rotationEffect(Angle(degrees: 270))
                .animation(.easeInOut(duration: 1.0), value: progress)

            // MARK: Timer
            VStack(spacing: 30) {
                // MARK: Elapsed Time
                VStack(spacing: 5) {
                    Text("Elapsed Time")
                        .opacity(0.7)

                    Text("0:00")
                        .font(.title)
                        .fontWeight(.bold)
                }
                .padding(.top)

                // MARK: Remaining Time
                VStack(spacing: 5) {
                    Text("Remaing Time")
                        .opacity(0.7)

                    Text("0:00")
                        .font(.title2)
                        .fontWeight(.bold)
                }
            }
        }
        .onAppear {
            progress = 0.5
        }
        .frame(width: 250, height: 250)
        .padding()
    }
}

struct ProgressRing_Previews: PreviewProvider {
    static var previews: some View {
        ProgressRing()
    }
}

'프로젝트 > FastingTimer' 카테고리의 다른 글

05. 기능 구현 #3  (0) 2023.02.20
04. 기능 구현 #2  (0) 2023.02.17
03. 기능 구현 #1  (0) 2023.02.16
02. 인터페이스 디자인 #2  (0) 2023.02.16
00. 시작하며  (0) 2023.02.09