SwiftUI + Combine을 이용하여 Fleek Copy app 만들기(3-2)
in Develop
이전 포스트에 이어, 오늘은 첫시간에 만든 IntroView가 나타난 후, 지난시간에 만든 탭뷰가 노출되도록 하는 작업을 진행해볼 예정입니다.
최초 introView가 노출되고, 로딩바가 꽉 차면, 인트로화면에서 메인탭 화면으로 이동하도록 하는것이 목표입니다.
그러기 위해서는 @State라는 키워드를 알아야 합니다.
- Apple Document에서 소개하는 대로 해석해보자면, 어떠한 값을 읽고 쓸수있는 property wrapper 타입입니다.
- 무슨말인지 잘 이해가 가지 않는다면, 역시 코드를 봐야겠습니다.
- isPlaying이라는 @State 키워드의 Bool 변수가 있습니다.
- Button은 isPlaying의 값에 따라 “Pause” : “Play” 라는 Label을 노출합니다.
- Button이 눌릴때마다, isPlaying,toggle()을 통해 true ↔ false로 변경됩니다.
Button의 label은 그럼 어떻게 될까요?
- 최초 isPlaying의 값은 false였기 떄문에, 최초 실행시에는 button의 label은 play로 노출됩니다.
- button을 누르게 되면 isPlaying 의 bool 값이 toggle되면서 play ↔ pause로 변경됩니다.
- @State로 선언된 프로퍼티는 변경될 때 마다, 해당 프로퍼티를 감시하고 있는 view들을 갱신해줍니다.
- 값이 변경될 떄마다 button이 갱신되며 label이 변경되는것입니다.
해당 키워드를 사용하여 저희는 isLoading이라는 변수를 선언하고,
progress가 100%가 되면 isLoading 변수를 toggle 해주는 방식으로 노출되어야 할 뷰를 갱신해보도록 하겠습니다.
import SwiftUI
import Combine
struct SplashView: View {
@State private var progress: Float = 0.0
@State private var isComplete: Bool = false
let duration: Double = 3.0
var timer: Publishers.Autoconnect<Timer.TimerPublisher> {
Timer.publish(every: 0.01, on: .main, in: .common).autoconnect()
}
var body: some View {
GeometryReader { geo in
Group {
if !isComplete {
VStack {
Text("FLEEK")
.foregroundColor(.red)
.fontWeight(.heavy)
.font(.largeTitle)
Text("당신의 운동을 의미있게!")
.foregroundColor(.white)
ProgressView(value: progress)
.animation(.linear(duration: duration), value: "")
.tint(.red)
.frame(width: 160)
}
.frame(minWidth: geo.size.width, maxWidth: .infinity, maxHeight: .infinity)
.background(.black)
} else {
MainTabView()
}
}
.onReceive(timer, perform: { _ in
if progress >= 1.0 {
isComplete = true
} else {
progress = min(1.0,progress + 0.004)
}
})
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
SplashView()
}
}
- State 키워드를 사용하여 progress와 isComplete 두개의 변수를 선언합니다.
- progress는 ProgressView의 진행률을 업데이트 하기 위한 변수이며
- isComplete는 ProgressView의 process가 완료되면 메인뷰를 보여주기 위한 변수입니다.
- 자동으로 프로그레스가 진행되도록 하기위해 Timer를 생성해 주었습니다.
- 매 0.01초마다 메인쓰레드의 런루프를 통해 호출되는 타이머를 생성합니다.
- onReceive 메소드는 Publisher가 방출한 Data를 감지합니다.
- 0.01초마다 타이머가 호출되며, 호출될 떄마다 onReceive 메소드의 클로져가 호출되며 progress(진행상태)를 업데이트 해줍니다.
- 1.0이 넘어가면 isComplete 값을 true로 변환해줍니다.
- 예상되는 동작은 다음과 같습니다.
- 최초 progress는 0이고, isComplete는 false이므로 introView가 보입니다.
- timer가 호출되고 progress가 1이 될 떄까지, progress에 0.004초씩 더해줍니다.
- progress 변수의 값이 변할때 마다 ProgressView는 @State 에 반응하여 변경되는 progress의 값만큼 진행도를 업데이트 해줍니다.
- progress가 1.0이 되면 isComplete의 값을 true로 설정합니다.
- isComplete가 true가 되면 뷰가 갱신되며, else { } 구문이 실행되어 mainView가 노출됩니다.
실제로 동작하는지 확인해 보도록 하겠습니다.
원하는대로 동작하는 것을 확인할 수 있습니다.
근데 뭔가 인트로(런치스크린) 화면이 노출되고 바로 메인이 나오는게, 어색합니다.
보통은 인트로화면이 splash 되면서 사라지고 메인화면이 나오는게 자연스러운데요.
다음 시간에는 splash되는 intro화면을 만들어 보도록 하겠습니다.