
| parSta! Project Main |
|---|
![]() |
| CustomTabView | SettingView | HomeView |
|---|---|---|
![]() | ![]() | ![]() |
| transiton을 활용한 커스텀 탭뷰 | Picker를 적용한 세팅뷰 | 다른 뷰들과 연결할 메인 뷰 |
프로젝트를 기획할 때, 하단의 3개의 탭을 통해
Setting,Home,Account의 뷰로 이동할 수 있도록 설계하였다.
처음엔 일반TabView를 사용하여 제작할까 했지만, 자유도가 떨어지고 원하는 애니메이션을 적용할 수 없을 것이라고 판단하여 직접 탭뷰를 만들어 구성하였다.
사용된 기능은 하기와 같다.
.transition- 뷰가 바뀔 때 애니메이션 적용enum- 열거형으로 뷰타입을 정의safeArea- 기종에 따른 탭뷰 사이즈 변경
먼저 세팅, 홈, 어카운트의 3가지 값을 확인하기 위해 열거형으로 값을 구분해준다.
enum TabIndex {
case home
case setting
case account
}
메인뷰에서 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)
하단에 버튼을 배치하고, 버튼을 클릭할 때마다 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)
}
지난번 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)

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

홈뷰는 스플래쉬뷰 이후에 보이는 뷰로, 본 앱의 MainView이다.
사용자의 프로필을 간단히 보여주고, 최신 뉴스를 보여주고, Swift Data나 Daily Quiz로 이동할 수 있는 버튼을 제공한다.
최신 뉴스의 경우 웹에서 데이터를 받아오는 형식으로는 구현이 어려워서 이미지를 넣고 이미지를 클릭하면 웹링크로 이동하도록 할 예정이다. 이번에는 아직 구현하지 않았고, 추후 웹뷰를 다룰 예정이다.
Swift Data와 Daily Quiz 버튼은 각각 상응하는 뷰로 이동할 수 있도록 하는 네이게이션 링크이다. Comming Soon은 추후 새로운 기능을 만들었을 때를 위해 만든 버튼으로, 클릭시 준비중인 기능이라는 메세지로Alert가 작동된다.
먼저 뷰 상단에 배치할 사용자의 미니 프로필을 작성하기로 한다. 랭크와 경험치는 아직 미구현이기 때문에 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)
}
iOS News는 지난 시간에 구현해보았던 AutoImageTransition을 활용하였다.
SwiftUI Asset 파일에 미리 사진을 추가해두고, Timer와 TabView를 활용하여 시간에 따라 자연스럽게 광고가 지나가도록 한다.
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]
}
}
}
}
}
버튼을 클릭했을 때 각각 버튼에 상응하는 뷰로 네비게이션을 통해 이동할 수 있도록 구현한다.
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")))
}
홈뷰를 네비게이션으로 만들면서, 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)
}

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