728x90
1. 결과물 미리보기
2. 프로젝트 구조
- View: View를 상속받은 클래스
- FrameworkListView
- FrameworkCell
- FrameworkDetailView
- FrameworkListView
- ViewModel: ObservableObject를 상속받은 클래스
- FrameworkListViewModel
- FrameworkDetailViewModel
- Data
- FrameworkData
- ViewController: UIViewControllerRepresentable을 상속받은 클래스
- SafariView
3. 세부 구현
3-1. Data 파악(FrameworkData.swift)
AppleFramework 구조체는 Hashable과 Identifiable을 상속받는다.
이는 추후 구현할 ForEach문의 구현과 protocol을 구현하기 위함이다.
3-2. 메인 View 구현(FrameworkListView.swift, FrameworkDetailView.swift)
기본적인 View클래스의 구조는 아래와 같다.
PreviewProvider를 통해 미리보기가 가능하다.
import SwiftUI
struct MyListView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
// Draw View
...View {
ForEach(viewModel.item) {
...
}
}
}
}
struct MyListView_Previews: PreviewProvider {
static var previews: some View {
MyListView()
}
}
NavigationView 및 NavigationLink를 구현하는 방법은 아래와 같다.
struct FrameworkListView: View {
@StateObject var vm = FrameworkListViewModel() // 화면에 뿌려줄 데이터
// 레이아웃 구조(4열)
let layout: [GridItem] = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible()),
]
var body: some View {
NavigationView {
ScrollView {
LazyVGrid(columns: layout) {
ForEach($vm.models) { $item in
// 내부 Cell 생성
FrameworkCell(framework: $item)
.onTapGesture {
vm.isShowingDetail = true
vm.selectedItem = item
}
}
// NavigationLink로 내부 View 표출
// 선택된 item으로 ViewModel을 구성하고, View를 만든다.
let des = FrameworkDetailView(viewModel: FrameworkDetailViewModel(framework: vm.selectedItem!))
NavigationLink(destination: des, isActive: $vm.isShowingDetail, label: { EmptyView() })
}
.padding([.top, .leading, .trailing], 16.0)
}
.navigationTitle("☀️ Apple Framework")
}
}
}
아래는 NavigationLink를 통해 호출된 항목(item)을 구성하는 View이다.
struct FrameworkDetailView: View {
@StateObject var viewModel: FrameworkDetailViewModel
var body: some View {
VStack(spacing: 30) {
Spacer()
Image(viewModel.framework.imageName)
.resizable()
.frame(width: 90, height: 90)
Text(viewModel.framework.name)
.font(.system(size: 24, weight: .bold))
Text(viewModel.framework.description)
.font(.system(size: 16, weight: .regular))
Spacer()
Button {
viewModel.isSafariPresented = true
} label: {
Text("Learn More")
.font(.system(size: 20, weight: .bold, design: .default))
.foregroundColor(.white)
}
.frame(maxWidth: .infinity, minHeight: 80)
.background(.pink)
.cornerRadius(40)
}
.padding(EdgeInsets(top: 0, leading: 30, bottom: 0, trailing: 30))
.sheet(isPresented: $viewModel.isSafariPresented) {
let url = URL(string: viewModel.framework.urlString)!
SafariView(url: url)
}
}
}
3-3. 세부 View 구현(FrameworkCell.swift, SafariView.swift)
첫 화면에서 GridView를 구성하는 아이콘을 표기하는 방법은 아래와 같다.
struct FrameworkCell: View {
@Binding var framework: AppleFramework
var body: some View {
VStack {
Image(framework.imageName)
.resizable()
.aspectRatio(contentMode: .fit)
Spacer()
Text(framework.name)
.font(.system(size: 16, weight: .bold))
.foregroundColor(Color.blue)
.multilineTextAlignment(.center)
Spacer()
}
}
}
항목 선택시 나타나는 화면에서 Learn more 버튼 클릭시 나타나는 SafariView는 아래와 같다.
이는 View를 상속받지 않고 UIViewControllerRepresentable을 상속받는다.
struct SafariView: UIViewControllerRepresentable {
let url: URL
func makeUIViewController(context:
UIViewControllerRepresentableContext<SafariView>) ->
SFSafariViewController {
SFSafariViewController(url: url)
}
func updateUIViewController(_ uiViewController:
SFSafariViewController, context:
UIViewControllerRepresentableContext<SafariView>) {}
}
3-4. ViewModel로 View와 Data 연결(FrameworkListViewModel.swift, FrameworkDetailViewModel.swift)
위의 코드에서도 볼 수 있듯이 @StateObject와 @Published 태그(Tag)로 Single source of truth 상태 관리를 할 수 있다.
그리고 이는 ObservableObject를 상속받은 클래스를 대상으로 한다.
신뢰성있게 변수와 데이터를 관리하는 방법이라고 생각하면 된다.
final class FrameworkListViewModel: ObservableObject {
@Published var models: [AppleFramework] = AppleFramework.list
@Published var isShowingDetail: Bool = false {
didSet {
print("isShowingDetail \(isShowingDetail)")
}
}
@Published var selectedItem: AppleFramework?
}
final class FrameworkDetailViewModel: ObservableObject {
@Published var framework: AppleFramework
@Published var isSafariPresented: Bool = false
init(framework: AppleFramework) {
self.framework = framework
}
}
4. 결론
SwiftUI는 HTML구조와 비슷해 직관적이면서도, 객체지향 개발을 적절히 가미해서 만들어진 것 같다.
MVVM 패턴을 잘 적용하도록하자!
728x90
'iOS' 카테고리의 다른 글
[iOS] 자주 사용하는 앱 구성요소 (0) | 2022.08.26 |
---|---|
[단축어] 아이폰으로 갤럭시처럼 전화/문자 보내기 (0) | 2022.08.15 |
[iOS] URLSession을 활용한 NetworkService 구현 (0) | 2022.07.23 |
[iOS] Combine (0) | 2022.07.23 |
[iOS] 네비게이션(Navigation)과 모달(Modality) (0) | 2022.07.22 |