-Preview-

parSta! Project Main

-프로젝트 진행상황-

  • SplashView 구현 / 미진행
  • CustomTabView 구현
  • SettingView 구현
  • HomeView 구현
  • AccountView 구현 / 프로필사진 선택, 닉네임 변경, 랭크정보 구현 필요
  • SwiftDataView 구현
  • DailyQuizView 구현 / 퀴즈 DB, 랜덤 퀴즈 구현 필요
  • RankSystem 구현 / RankInfo, Rank 사용자 정보 저장 등 구현 필요

-금일 구현한 뷰-

CustomTabViewSettingViewHomeView
transiton을 활용한 커스텀 탭뷰Picker를 적용한 세팅뷰다른 뷰들과 연결할 메인 뷰

-View Review-

1. CustomTabView

프로젝트를 기획할 때, 하단의 3개의 탭을 통해 Setting, Home, Account의 뷰로 이동할 수 있도록 설계하였다.
처음엔 일반 TabView를 사용하여 제작할까 했지만, 자유도가 떨어지고 원하는 애니메이션을 적용할 수 없을 것이라고 판단하여 직접 탭뷰를 만들어 구성하였다.
사용된 기능은 하기와 같다.

  • .transition - 뷰가 바뀔 때 애니메이션 적용
  • enum - 열거형으로 뷰타입을 정의
  • safeArea - 기종에 따른 탭뷰 사이즈 변경

1) 열거형으로 탭 뷰 구분하기

먼저 세팅, 홈, 어카운트의 3가지 값을 확인하기 위해 열거형으로 값을 구분해준다.

enum TabIndex {
    case home
    case setting
    case account
}

2) transition을 활용한 애니메이션 적용

메인뷰에서 ZStack을 활용하여 사용자에게 보여질 뷰를 최하단에 배치한다. 이 때, 보여질 뷰의 조건을 if로 하여 현재 tabIndex의 값에 따라 바뀌도록 하고 .transition을 사용하여 애니메이션 효과를 부여한다.

ZStack {
	// tabIndex: TabIndex = .home
	if tabIndex == .home {
		MainTitleView()
			.transition(.opacity)
	} else if tabIndex == .setting {
		SettingView()
			.transition(.opacity)
	} else if tabIndex == .account {
		AccountView()
			.transition(.opacity)
	}
}
.animation(.smooth, value: self.tabIndex)

3) 버튼을 통한 인덱스값 변경

하단에 버튼을 배치하고, 버튼을 클릭할 때마다 tabIndex의 값이 변하도록 만든다.

Button(action: {
	// 버튼을 누르면 tabIndex의 값이 account로 변경
	self.tabIndex = .account
}) {
	VStack {
    	// tabIndex의 값에 따라 이미지 변경
		Image(systemName: self.tabIndex == .account ? "person.fill" : "person")
			.font(.title3)
			.fontWeight(.regular)
                            
		Text("Account")
			.font(.subheadline)
			.fontWeight(.regular)
	}
    // tabIndex의 값에 따라 색 변경
	.foregroundStyle(self.tabIndex == .account ? Color.parsta : Color.parstaGray)
}

4) SafeArea 조정

지난번 SideMenu를 구현했을 때와 마찬가지로 기종에 따라 탭뷰의 크기가 변할 수 있도록 하는 함수를 구현하고, 이를 코드에 적용시킨다.

private func safeAreaBottomSizeCheck() -> CGFloat {
    let scenes = UIApplication.shared.connectedScenes
    let windowScene = scenes.first as? UIWindowScene
    let window = windowScene?.windows.first
    
    let safeAreaBottomSize = window?.safeAreaInsets.bottom
    let paddingValue = CGFloat(safeAreaBottomSize == 0 ? 10 : 0)
    
    return paddingValue
}

Rectangle()
	// 기종에 따라 Rectangle의 height값이 변경되며 탭뷰의 크기가 변함
	.frame(height: safeAreaBottomSizeCheck())
	.foregroundStyle(Color.parstaGray200)

5) 구현 결과물


2. SettingView

세팅뷰는 앱의 옵션을 설정하는 뷰로, 언어와 개인보호설정, 고객센터 등의 기능을 가진다.
다만, 언어를 변경하는 기능은 시간이 많이 필요하기 때문에 이번에는 UI만 구성하였고, Help 기능은 클릭시 Alert를 통해 제작자의 정보를 보여줄 생각이다.

1) 전체 코드

import SwiftUI

enum LanguageSetting {
    case english
    case korean
}

struct SettingView: View {
    
    @State private var language: LanguageSetting = .english
    
    var body: some View {
        
        List {
            
            HStack {
                Image(systemName: "globe")
                    .font(.subheadline)
                    .fontWeight(.medium)
                    .foregroundStyle(Color.parstaGray)
                
                Text("Language")
                    .font(.subheadline)
                    .fontWeight(.medium)
                    .foregroundStyle(Color.black)
                
                Spacer()
                
                // 픽커를 통해 언어 설정을 변경할 수 있도록 구현
                Picker("", selection: $language) {
                    Text("English (US)").tag(LanguageSetting.english)
                    
                    Text("Korean (KR)").tag(LanguageSetting.korean)
                }
            }
            
            HStack {
                Image(systemName: "lock.shield")
                    .font(.subheadline)
                    .fontWeight(.medium)
                    .foregroundStyle(Color.parstaGray)
                
                Text("Privacy Policy")
                    .font(.subheadline)
                    .fontWeight(.medium)
                    .foregroundStyle(Color.black)
            }
            
            HStack {
                Image(systemName: "info.circle")
                    .font(.subheadline)
                    .fontWeight(.medium)
                    .foregroundStyle(Color.parstaGray)
                
                Text("Help center")
                    .font(.subheadline)
                    .fontWeight(.medium)
                    .foregroundStyle(Color.black)
            }
            
        }
        .listStyle(.plain)
    }
}

2) 구현 결과물


3. HomeView

홈뷰는 스플래쉬뷰 이후에 보이는 뷰로, 본 앱의 MainView이다.
사용자의 프로필을 간단히 보여주고, 최신 뉴스를 보여주고, Swift Data나 Daily Quiz로 이동할 수 있는 버튼을 제공한다.
최신 뉴스의 경우 웹에서 데이터를 받아오는 형식으로는 구현이 어려워서 이미지를 넣고 이미지를 클릭하면 웹링크로 이동하도록 할 예정이다. 이번에는 아직 구현하지 않았고, 추후 웹뷰를 다룰 예정이다.
Swift Data와 Daily Quiz 버튼은 각각 상응하는 뷰로 이동할 수 있도록 하는 네이게이션 링크이다. Comming Soon은 추후 새로운 기능을 만들었을 때를 위해 만든 버튼으로, 클릭시 준비중인 기능이라는 메세지로 Alert가 작동된다.

1) 사용자 미니 프로필 작성

먼저 뷰 상단에 배치할 사용자의 미니 프로필을 작성하기로 한다. 랭크와 경험치는 아직 미구현이기 때문에 UI만 구현한 상태이다.

// 지오메트리를 통해 위치정확도를 높이기
GeometryReader { proxy in
	// 뷰 전체를 스크롤뷰로 감싸 자연스럽게 만들기
	ScrollView {
		VStack(spacing: 0) {
			Group {
				Image("Logo")
					.resizable()
					.scaledToFit()
					.frame(width: 120, height: 60)
					.padding(.bottom, 15)
					.offset(x: -proxy.size.width / 3.5)
                        
				HStack(spacing: 15) {
					Circle()
						.frame(width: 50)
						.foregroundStyle(Color.secondary)
						.offset(y: -25)
                            
					VStack(alignment: .leading) {
                    	// 추후 사용자가 닉네임을 자유롭게 바꿀 수 있도록 조정
						Text("Kim Sparta")
							.font(.system(size: 25))
							.fontWeight(.bold)
                         
                        // 사용자의 랭크와 경험치 상태를 표시하는 뷰
						RankDataView()
					}
				}
				.offset(x: -proxy.size.width / 20)
			}

2) 최신 뉴스 구현

iOS News는 지난 시간에 구현해보았던 AutoImageTransition을 활용하였다.
SwiftUI Asset 파일에 미리 사진을 추가해두고, TimerTabView를 활용하여 시간에 따라 자연스럽게 광고가 지나가도록 한다.

import SwiftUI

struct NewsView: View {
    // 뉴스에 보여질 사진 배열 생성
    private let newsImage: [String] = ["news1",
                                       "news2",
                                       "news3",
                                       "news4",
                                       "news5"]
    @State private var imageCount: Int = 0
    
    // 현재 사진값
    @State private var selectedImage: String = "news1"
    
    // 3초 주기로 이벤트를 발생시키는 타이머 생성
    private var timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
    
    var body: some View {
        
        VStack {
            Text("iOS News")
                .font(.headline)
                .fontWeight(.medium)
                .offset(x: -140)
            
            TabView(selection: $selectedImage) {
            	// ForEach문으로 배열의 멤버 수만큼 뉴스 이미지를 생성
                ForEach(newsImage, id: \.self) { item in
                    Image(item)
                        .resizable()
                        .scaledToFill()
                        .frame(width: 350, height: 160)
                        .clipShape(RoundedRectangle(cornerRadius: 10))
                }
            }
            .tabViewStyle(.page)
            .frame(height: 180)
            // 타이머가 이벤트를 발생시키면 값을 받아와 내부코드를 반환
            .onReceive(self.timer) { _ in
                withAnimation {
                    self.imageCount = (imageCount + 1) % newsImage.count
                    self.selectedImage = self.newsImage[self.imageCount]
                }
            }
        }
    }
}

3) 하단 버튼 구현

버튼을 클릭했을 때 각각 버튼에 상응하는 뷰로 네비게이션을 통해 이동할 수 있도록 구현한다.
Comming Soon버튼은 Alert를 통해 경고를 띄우도록 구현하였다.

NavigationLink(destination: SwiftDataView()) {
	ZStack {
		RoundedRectangle(cornerRadius: 10)
			.frame(width: 350, height: 40)
			.foregroundStyle(Color.parsta)
                                    
		RoundedRectangle(cornerRadius: 10)
			.frame(width: 347, height: 37)
			.foregroundStyle(Color.white)
                                    
		Text("Swift Data")
			.font(.system(size: 20))
			.fontWeight(.medium)
			.foregroundStyle(Color.parsta)
			.lineLimit(1)
	}
}

Button(action: {
	self.isShowingCommingSoon = true
}) {
	ZStack {
		RoundedRectangle(cornerRadius: 10)
			.frame(width: 350, height: 40)
			.foregroundStyle(Color.parsta)
                                    
		RoundedRectangle(cornerRadius: 10)
			.frame(width: 347, height: 37)
			.foregroundStyle(Color.white)
                                    
		Text("Comming Soon...")
			.font(.system(size: 20))
			.fontWeight(.medium)
			.foregroundStyle(Color.parsta)
			.lineLimit(1)
	}
}
// 클릭시 경고를 띄움
.alert(isPresented: $isShowingCommingSoon) {
	.init(title: Text("Comming Soon..."), message: Text("This feature is being prepared."), dismissButton: .default(Text("OK")))
}

4) dissmiss 구현

홈뷰를 네비게이션으로 만들면서, Swift Data뷰로 이동했을 때, navigationBar때문에 기존 뷰의 위치가 틀어지는 문제가 발생했다. 때문에 .navigationBarHidden()기능을 통해 navigationBar를 숨기고 뒤로가기버튼을 커스텀하여 만들었다.

@Environment(\.dismiss) private var dismiss // dismiss 버튼 선언

Button(action: {
	// 버튼 클릭시 dismiss가 작동되어 현재 뷰가 cancil됨
	dismiss()
}) {
	Image(systemName: "chevron.backward")
		.font(.system(size: 25))
		.foregroundStyle(Color.parstaGray)
}

5) 구현 결과물


-오늘의 학습후기-

오늘은 팀프로젝트에서 내가 맡지 않았던 부분들을 구현해보았다.
생각했던 것보다 시간이 오래걸리지 않아서 예전보다 실력이 늘었음을 알 수 있었고, 바로 이전에 공부했던 내용들을 활용하여 제작을 진행하니 무척 수월하게 제작을 할 수 있었다.
dismiss의 경우 처음 써보는 기능이었는데, 앞으로 요긴하게 쓸 수 있을 것 같다.
또, 커스텀탭뷰를 구현하면서 애니메이션이 제대로 적용되지 않고 딱딱하게 뷰가 변하는 탓에 고민이 많았는데, transition 기능을 사용하여 원하는 형태로 무사히 구현할 수 있었다.
profile
이유있는 코드를 쓰자!!

0개의 댓글