본문 바로가기

프로젝트/FastingTimer

02. 인터페이스 디자인 #2

인터페이스 디자인 #2
ContentView


ContentView
| Header

body의 가독성을 높이기 위해 구성되는 View들은 extension으로 구현한다.
header는 현재 상태를 문장으로 표시하기 위한 TextView와 이후 Fasting Plan을 변경하는 버튼을 포함한다.

struct ContentView: View {
    var body: some View {
        VStack {
            header

            ProgressRing()
        }
    }
}

extension ContentView {
    // MARK: Title, Fasting Plan
    var header: some View {
        VStack {
            Text("Let's get started!")
                .font(.headline)
                .foregroundColor(Color(#colorLiteral(red: 0.8567120433, green: 0.5915268064, blue: 1, alpha: 1)))
        }
    }
}

진행링과 같은 색으로 현재 상태를 표시한다.
지금은 임시 문자열을 표시하지만 이후엔 상태에 맞는 문자열을 표시하도록 구현한다.

import SwiftUI
import SwiftUIVisualEffects

struct ContentView: View {
    var body: some View {
        VStack {
            header

            ProgressRing()
        }
    }
}

extension ContentView {
    var header: some View {
        VStack {
            // MARK: Title
            Text("Let's get started!")
                .font(.headline)
                .foregroundColor(Color(#colorLiteral(red: 0.8567120433, green: 0.5915268064, blue: 1, alpha: 1)))

            // MARK: Fasting Plan
            Text("16:8")
                .fontWeight(.bold)
                .padding(.horizontal, 24)
                .padding(.vertical, 8)
                .background(BlurEffect().blurEffectStyle(.systemThickMaterial))
                .cornerRadius(25)
        }
    }
}

아래쪽엔 현재 진행 중인 Fasting Plan을 표시한다.
마찬가지로 임시로 표시하고 있지만 사용자가 선택한 Fasting Plan을 알맞게 표시하도록 구현한다.

Capsule 모양의 배경은 호환성을 위해 SwiftUI의 Matetial 대신 swiftui visual effects 패키지를 사용했다.

 

SwiftUI에서 Blur를 사용하는 4가지 방법

Blur Apple Developer Documentation developer.apple.com struct ContentView: View { var body: some View { ZStack() { Image("bg.sample") .resizable() .ignoresSafeArea() .scaledToFill() .blur(radius: 20) Text("Blur") .font(.largeTitle) .foregroundColor(.white)

chillog.page

자세한 내용은 위 글을 참고하면 된다.

ContentView
| Times

extension ContentView {
    var header: some View {
        .
        .
        .
    }

    var times: some View {
        HStack(spacing: 60) {
            // MARK: Start Time
            VStack(spacing: 5) {
                Text("Start")
                    .opacity(0.7)

                Text(Date(), formatter: Self.DateFormat)
                    .fontWeight(.bold)
            }

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

                Text(Date(), formatter: Self.DateFormat)
                    .fontWeight(.bold)
            }
        }
    }
}

times는 시작 시간과 끝나는 시간을 표시한다.
Date를 String으로 치환해 사용하며, 이러한 방식은 iOS의 버전에 따라 두 가지 방식이 존재한다.

Text(Date(), format: .dateTime.weekday().hour().minute().second())
    .fontWeight(.bold)

iOS15부터는 Text(_:format:)을 사용하고, format 파라미터에 .dateTime으로 시작하는 체이닝을 전달해 변환할 수 있다.

static let DateFormat: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "E h:mm:ss a"
        return formatter
    }()

Text(Date(), formatter: Self.DateFormat)
    .fontWeight(.bold)

iOS15 이전까지는 DateFormatter를 정의하고 Text(_:formatter:)의 formatter 파라미터에 전달해 변환한다.

더보기

Source

extension ContentView {
    var header: some View {
        .
        .
        .
    }

    var times: some View {
        HStack(spacing: 60) {
            // MARK: Start Time
            VStack(spacing: 5) {
                Text("Start")
                    .opacity(0.7)

                if #available(iOS 15.0, *) {
                    Text(Date(), format: .dateTime.weekday().hour().minute().second())
                        .fontWeight(.bold)
                } else {
                    Text(Date(), formatter: Self.DateFormat)
                        .fontWeight(.bold)
                }

            }

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

                Text(Date().addingTimeInterval(16), formatter: Self.DateFormat)
                    .fontWeight(.bold)
            }
        }
    }
}

extension ContentView {
    // MARK: DateFormat
    static let DateFormat: DateFormatter = {
            let formatter = DateFormatter()
            formatter.dateFormat = "E h:mm:ss a"
            return formatter
        }()
}

분기 코드는 위와 같고

결과는 사진과 같다.
formatter를 사용해도 조금 귀찮을 뿐이지 iOS15와 동일하게 구현하는 것이 얼마든지 가능하다.

ContentView
| strBtn

extension ContentView {
    var header: some View {
        .
        .
        .
    }

    var times: some View {
        .
        .
        .
    }

    var strBtn: some View {
        Button {
            // MARK: Button Action
        } label: {
            Text("Start Fasting")
                .font(.title3)
                .fontWeight(.bold)
                .padding(.horizontal, 24)
                .padding(.vertical, 8)
                .background(BlurEffect().blurEffectStyle(.systemThickMaterial))
                .cornerRadius(25)
        }
        .foregroundColor(.primary)
    }
}

가장 아래에는 Timer를 시작 할 버튼을 하나 배치한다.

foregroundColor를 단순한 black이 아닌 primary를 사용하면 Scheme에 따라 적절히 반응하는 UI를 구현할 수 있다.

struct ContentView: View {
    var body: some View {
        VStack(spacing: 40) {
            header

            ProgressRing()

            times

            strBtn
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
    }
}

마지막으로 정렬과 간격을 적당히 손 보면 완성이다.

더보기

Source

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

import SwiftUI
import SwiftUIVisualEffects

struct ContentView: View {
    var body: some View {
        VStack(spacing: 40) {
            header

            ProgressRing()

            times

            strBtn
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

extension ContentView {
    var header: some View {
        VStack(spacing: 40) {
            // MARK: Title
            Text("Let's get started!")
                .font(.headline)
                .foregroundColor(Color(#colorLiteral(red: 0.8567120433, green: 0.5915268064, blue: 1, alpha: 1)))

            // MARK: Fasting Plan
            Text("16:8")
                .fontWeight(.bold)
                .padding(.horizontal, 24)
                .padding(.vertical, 8)
                .background(BlurEffect().blurEffectStyle(.systemThickMaterial))
                .cornerRadius(25)
        }
    }

    var times: some View {
        HStack(spacing: 60) {
            // MARK: Start Time
            VStack(spacing: 5) {
                Text("Start")
                    .opacity(0.7)

                if #available(iOS 15.0, *) {
                    Text(Date(), format: .dateTime.weekday().hour().minute().second())
                        .fontWeight(.bold)
                } else {
                    Text(Date(), formatter: Self.DateFormat)
                        .fontWeight(.bold)
                }

            }

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

                Text(Date().addingTimeInterval(16), formatter: Self.DateFormat)
                    .fontWeight(.bold)
            }
        }
    }

    var strBtn: some View {
        Button {
            // MARK: Button Action
        } label: {
            Text("Start Fasting")
                .font(.title3)
                .fontWeight(.bold)
                .padding(.horizontal, 24)
                .padding(.vertical, 8)
                .background(BlurEffect().blurEffectStyle(.systemThickMaterial))
                .cornerRadius(25)
        }
        .foregroundColor(.primary)
    }
}

extension ContentView {
    // MARK: DateFormat
    static let DateFormat: DateFormatter = {
            let formatter = DateFormatter()
            formatter.dateFormat = "E h:mm:ss a"
            return formatter
        }()
}

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

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