Flutter앱을 만들어보기 전에 SwiftUI를 통해 만들 예제를 구상하고 있었다.
근데 할게 없길래 iOS처음 입문할 때 만들어둔 예제가 생각나서 SwiftUI로 구현해봤다.
뭐 데이터 전달이나 간단한 뷰 구성이니까 나쁘지 않을것도 같으니까...
본문의 설명은 본인이 공부하면서 이해했던 내용을 적는것이니까..
코드는 정상적으로 작동해도 설명은 믿지 마시오...
![]() | ![]() | ![]() | ![]() |
|---|
(markdown은 안써봐서 잘 보이나 모르겠네...)
여튼 완성본은 위와 같이 되어있다.
코드를 보도록하자
import Foundation
import SwiftUI
class MainViewModel: ObservableObject {
@Published var ledString: String = "Led String"
@Published var ledStringSize: CGFloat = 70
@Published var ledStringColor : Color = .black
@Published var ledBoardColor : Color = .white
@Published var selectedBoardColor: Color? = nil
@Published var selectedStringColor: Color? = nil
}
MainViewModel은 View를 구성하는 데이터만을 담고있다
왜냐? MainViewModel은 MainView를 구성하기만 할 뿐이니까..
(너무 당연한가...)
struct MainView: View {
@StateObject var mainViewModel = MainViewModel()
var body: some View {
NavigationView {
ZStack(){
mainViewModel.ledBoardColor.ignoresSafeArea()
Text(mainViewModel.ledString)
.font(.system(size: mainViewModel.ledStringSize))
.fontWeight(.semibold)
.foregroundStyle(mainViewModel.ledStringColor)
}
.navigationTitle("Led Board 2")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
NavigationLink(destination: SettingView(settingViewModel: SettingViewModel(mainViewModel: mainViewModel))) {
Text("설정")
.foregroundStyle(.gray)
}
}
}
}
}
}
@StateObject var mainViewModel = MainViewModel()
MainView에서 MainViewModel은 StateObject로 선언되었다.
Dart와 비슷하게 SwiftUI에서도 State, Published로 선언되면 변화를 감지하여
View를 그릴 수 있기 때문이다.
이 이유로 MainViewModel을 감지하기 위한 StateObject로 선언되었다.
이제 MainViewModel의 변화가 감지된다면 MainView도 따라 바뀔 준비가 되었다.
ZStack {
mainViewModel.ledBoardColor.ignoresSafeArea()
...
}
.navigationTitle("Led Board 2")
.navigationBarTitleDisplayMode(.inline)
ZStack()을 사용한 이유는 BackroundColor를 바꾸기 위해서 사용했다.
왜 굳이 ZStack이어야 하냐? 화면전체에 색을 변경하려면 ZStack말고 없다.
(다른 스택으로 한다면 아마 해당스택의 frame만큼의 영역만 해당될 것 같다.)
그리고 navigationTitle이 생각보다 바뀌었는데 예전에는
.navigationBarTitle("led board 2", displayMode:.inline)
형식으로 구성되었던게 navigationTitle로 rename되었다.
(근데 그럴거면 Displaymode는 그냥 분리안하고 파라미터만 넣어도 되는거 아닌가...)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
NavigationLink(destination: SettingView(settingViewModel: SettingViewModel(mainViewModel: mainViewModel))) {
Text("설정")
.foregroundStyle(.gray)
}
}
}
navigationBarItem은 24-03-19기준 지원하지 않는다고 하며 toolBar로 대체되었으니 알고있기를..
사실 이부분은 설명하려했던건 아니고 NavigationLink를 설명하려 했다.
NavigationLink의 destinaion을 보면 살짝 복잡할 수 있는데
SettingView(settingViewModel: SettingViewModel(mainViewModel: mainViewModel))
-> MainView에서 SettingView로 이동하는 링크를 생성한다.
-> SettingView에서 SettingViewModel을 생성하고, mainViewModel을 전달한다.
-> SettingViewModel은 mainViewModel을 감시하고, 변경 사항을 반영한다.
=> 즉, SettingViewModel에서 mainViewModel의 데이터를 사용하거나 변경할 수 있게 되었다.
이번 프로젝트에서 가장 중요한 기능이 이 부분이다.
해당 코드가 없다면 ViewModel간의 데이터 처리가 이루어지지 않는다.
그렇다면 MVVM패턴을 쓸 이유가 없을 뿐더러 View에서 데이터를 저장하고 관리한다면 단일 책임 원칙에도 위배되기 때문이다.
import Foundation
import SwiftUI
import UIKit
class SettingViewModel: ObservableObject {
@ObservedObject var mainViewModel = MainViewModel()
init(mainViewModel: MainViewModel) {
self.mainViewModel = mainViewModel
}
func createTextField() -> some View {
TextField("텍스트를 입력해주세요",
text: $mainViewModel.ledString)
.multilineTextAlignment(.center)
}
func createBoardColor(color: Color) -> some View {
Button {
self.mainViewModel.ledBoardColor = color
self.mainViewModel.selectedBoardColor = color
print(self.mainViewModel.ledBoardColor)
} label: {
Circle().fill(color)
.frame(width: 50)
.opacity(self.mainViewModel.selectedBoardColor == color ? 1.0 : 0.5)
}
}
func createStringColor(color: Color) -> some View {
Button {
self.mainViewModel.ledStringColor = color
self.mainViewModel.selectedStringColor = color
print(self.mainViewModel.ledStringColor)
} label: {
Circle().fill(color)
.frame(width: 50)
.opacity(self.mainViewModel.selectedStringColor == color ? 1.0 : 0.5)
}
}
func createSlider() -> some View {
VStack {
Text("폰트 사이즈 크기")
Slider(
value: $mainViewModel.ledStringSize,
in: 0...100,
step: 1
)
.padding(10)
}
}
}
위는 전체 코드인데 view를 그리는 함수도 여기에 작성했는데...
아마 이것도 잘못된걸지도 모른다.
코드를 살펴보면
@ObservedObject var mainViewModel = MainViewModel()
init(mainViewModel: MainViewModel) {
self.mainViewModel = mainViewModel
}
이렇게 mainViewModel을 받아 초기화 하는 생성자를 넣어두었는데 이 생성자를 빠뜨리면 View와 ViewModel이 연결되지 않는다.
연결이 되지 않는다면 View가 VieModel을 사용하도록 설정하지 못한다는 뜻이고,
ViewModel간의 데이터를 전달하지 못한다는 뜻이 된다.
여기가 가장 중요한 부분이고 나머지는 전부 View생성 관련된 함수이니 넘어가도록 하겠다.
struct SettingView: View {
@StateObject var settingViewModel : SettingViewModel
var body: some View {
GeometryReader { geo in
VStack(alignment: .center) {
settingViewModel.createTextField()
.padding(10)
.frame(width: geo.size.width, height: geo.size.height/2)
.border(Color.green)
HStack {
Text("Led Board 색상 변경")
.padding(.leading,15)
Spacer()
}
HStack() {
settingViewModel.createBoardColor(color: .blue)
Spacer()
settingViewModel.createBoardColor(color: .red)
Spacer()
settingViewModel.createBoardColor(color: .purple)
Spacer()
settingViewModel.createBoardColor(color: .black)
Spacer()
settingViewModel.createBoardColor(color: .yellow)
} // LedBoard Color Change
.padding(EdgeInsets(top: 20, leading: 10, bottom: 20, trailing: 10))
.border(Color.black)
HStack {
Text("Led String 색상 변경")
.padding(.leading,15)
Spacer()
}
HStack() {
settingViewModel.createStringColor(color: .blue)
Spacer()
settingViewModel.createStringColor(color: .red)
Spacer()
settingViewModel.createStringColor(color: .purple)
Spacer()
settingViewModel.createStringColor(color: .black)
Spacer()
settingViewModel.createStringColor(color: .yellow)
} // LedString Color Change
.padding(EdgeInsets(top: 20, leading: 10, bottom: 20, trailing: 10))
.border(Color.blue)
settingViewModel.createSlider()
}
.border(Color.red)
}
}
}
SettingView()도 딱히 볼게 없다.
SettingViewModel을 StateObject로 설정한건 단순히 View와 ViewModel의 소유관계를 유지하기 위해서이다.
다음 글은 해당 애플리케이션을 Flutter로 만들어볼까 한다.
(후기)
설명 안맞음 주의, 뭐 바뀌었다는건 내가 공부했었을 시점 기준이라 믿지 말것
욕만 하지 말아줘요...