추후에 제대로 정리할 예정.. 우선은 아무렇게나 기록 부터..
// 삼항 연산자 사용 예시
@State private var selectedFilter: TweetFilterViewModel = .tweets
...
HStack {
ForEach(TweetFilterViewModel.allCases, id: \.rawValue) { item in
VStack {
Text(item.title)
.font(.subheadline)
.fontWeight(selectedFilter == item ? .semibold : .regular)
.foregroundColor(selectedFilter == item ? .black : .gray)
}
}
}struct TextArea: View {
@Binding var text: String
let placeholder: String
init(_ placeholder: String, text: Binding<String>) {
self.placeholder = placeholder
self._text = text
}
...
}
이를 추후 필요한 코드 부분에서 다음과 같이 호출 가능함
let myTextArea = TextArea("Enter text here", text: $myText)private struct ProfileImageModifier: ViewModifier {
func body(content: Content) -> some View {
content
.foregroundColor(Color(.systemBlue))
.scaledToFill()
.frame(width: 180, height: 180)
.clipShape(Circle())
}
}MVVM 패턴에서 ViewModel을 enum 또는 struct로 정의하는 것은 두 가지 다른 접근 방식입니다. 두 가지 방식 모두 ViewModel의 역할을 수행할 수 있으며, 선택하는 방식은 개인의 선호나 프로젝트의 요구 사항에 따라 다릅니다.
Enum을 사용하는 경우, ViewModel의 상태를 명확하게 정의할 수 있습니다. 각 상태는 명시적으로 정의되어 있으며, 상태 전이를 처리하는 데 유용합니다. 또한 enum은 연관된 값을 포함할 수 있으므로, ViewModel에서 특정 동작을 수행하는 데 필요한 모든 데이터를 포함할 수 있습니다.
반면, struct를 사용하는 경우, ViewModel의 구성 요소를 더 자세히 제어할 수 있습니다. 구조체는 클래스와 달리 값 형식이므로 복사 및 전달이 쉽습니다. 또한 struct를 사용하면 코드를 조직화하고 관련 데이터 및 동작을 묶을 수 있습니다.
따라서 선택은 개인의 취향이나 프로젝트의 요구 사항에 따라 다를 수 있습니다. Enum을 사용하면 ViewModel의 상태 전이를 관리하기 쉽지만, struct를 사용하면 ViewModel을 더 세분화하고 데이터와 동작을 더 잘 제어할 수 있습니다.
ViewModel에서 enum을 사용하는 가장 큰 의의는 얼마나 수정이 쉽냐는 것에 있다.
예를 들어 viemodel에 있는 많은 option(case) 중 하나를 삭제한다고 했을 때, viewModel에서만 삭제 작업을 진행 해주어도 view에서는 알아서 해당 사항이 잘 반영되기 때문이다. 추가의 경우에도 마찬가지.
→ 즉, single source of truth를 잘 지킨다고 할 수 있다. (원 파일에서 모두 컨트롤 가능하기 때문)
UITextView.appearance().backgroundColor = .clear 코드는 SwiftUI의 TextEditor view를 커스터마이징하기 위해 사용되는 코드입니다.
SwiftUI의 TextEditor view는 실제로 UITextView를 기반으로 하며, UITextView의 배경색은 흰색입니다. 하지만 TextArea view의 배경색은 투명해야 하기 때문에, TextEditor의 배경색을 투명하게 만들어줘야 합니다.
따라서 UITextView의 appearance를 사용하여 전역적으로 모든 UITextView의 배경색을 투명으로 설정하는 것입니다. 이는 TextEditor view에 적용되어 배경색이 투명해지게 됩니다.
즉, UITextView의 appearance를 사용하여 TextEditor view의 기본 설정을 변경하여 사용자 정의를 수행하는 것입니다.
import SwiftUI
struct TextArea: View {
@Binding var text: String
let placeholder: String
init(_ placeholder: String, text: Binding<String>) {
self.placeholder = placeholder
self._text = text
UITextView.appearance().backgroundColor = .clear
}
var body: some View {
ZStack(alignment: .topLeading) {
TextEditor(text: $text)
.padding(4)
if text.isEmpty {
Text(placeholder)
.foregroundColor(Color(.placeholderText))
.padding(.horizontal, 8)
.padding(.vertical, 12)
}
}
.font(.body)
}
}
또한, SwiftUI의 TextEditor에는 placeholder가 없기 때문에 위와 같이 직접 ZStack으로 구현해주어야 한다.
import SwiftUI
// rounded corner를 주는 Shape struct
// View에서 .clipshape 수정자를 통해 .clipShape(RoundedShape(corners: [.bottomRight]))와 같은 식으로 사용 가능.
struct RoundedShape: Shape {
var coners: UIRectCorner
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: coners, cornerRadii: CGSize(width: 80, height: 80))
return Path(path.cgPath)
}
}
위와 같이 RoundedShape를 커스텀하여 따로 Componenets로 정의해두면 추후에 View에서 원하는 곳의 corner를 쉽게 줄 수 있음.
import SwiftUI
import Firebase
@main
struct TwitterSwiftUICloneApp: App {
@StateObject var viewModel = AuthViewModel()
init() {
FirebaseApp.configure()
}
var body: some Scene {
WindowGroup {
NavigationView {
ContentView()
}
.environmentObject(viewModel)
}
}
}
앱을 관통하여 여러 곳에서 사용되어야 하는 Auth와 같은 ViewModel들은 어디선가 초기화가 되어야 사용 할 수 있음. 따라서 위의 코드와 같이 root App file에서 @StateObject로 viewModel을 만들어주고, .environmentObject로 ContenView에 launch 해줄 것.
https://showcove.medium.com/swift-struct-vs-class-1-68cf9cbf87ca
무작정 struct는 좋고 class는 지양해야 하는 생각 하지 말 것. 효용성의 목적이 다를 뿐.
func login(withEmail email: String, password: String) {
print("DEBUG: Login with email \(email)")
}
func register(withEmail email: String, password: String, fullname: String, username: String) {
print("DEBUG: Register with email \(email)")
}
때론 함수를 호출할 때 각각의 인자에 이름을 붙이는게 유용할 때가 있는데 함수로 던져진 각 인자의 목적을 가르키기 위함.
함수 사용자에게 함수를 호출 할 때 인자에 이름을 지어주길 원하면 각 인자에게 외부 인자 이름을 지역 인자 이름에 붙이도록 정의함.
지역 인자 이름 앞에 한칸 띄우고 외부 인자 이름을 작성함.
ViewModel에서 변수를 @Published로 정의해주면 해당 변수에 변화가 생겼을 때 View에 즉각 Notificate하여 변화를 적용시킬 수 있음
@Published var currentUser: User? // currentUser를 nil로 하는 이유는 앱을 실행하고 Data를 Fetch 하기 전까지 아주 짧은 시간이지만 반드시 nil이 되기 때문에 충돌을 방지하기 위함.
// way 1 (with init func, dependency injection 사용)
private let user: User
init(user: User) {
self.user = user
}
...
Text(user.fullname)
.font(.title2).bold()
---
// way 2 (without init func, just set viewModel)
@EnvironmentObject var authViewModel: AuthViewModel
...
var body: some View {
if let user = authViewModel.currentUser {
VStack(alignment: .leading, spacing: 32) {
VStack(alignment: .leading) {
KFImage(URL(string: user.profileImageUrl)) // Kingfisher 라이브러리를 사용하여 이미지 fetch
.resizable()
.scaledToFill()
.clipShape(Circle())
.frame(width: 48, height: 48)
VStack(alignment: .leading, spacing: 4) {
Text(user.fullname)
.font(.headline)
Text("@\(user.username)")
.font(.caption)
.foregroundColor(.gray)
}
UserStatsView()
.padding(.vertical)
}
...
상황에 맞게 적절한 방법 선택하여 사용할 것
import SwiftUI
struct MainTabView: View {
@State private var selectedIndex = 0
var body: some View {
TabView(selection: $selectedIndex) {
FeedView()
.onTapGesture {
self.selectedIndex = 0
}
.tabItem {
Image(systemName: "house")
}.tag(0)
ExploreView()
.onTapGesture {
self.selectedIndex = 1
}
.tabItem {
Image(systemName: "magnifyingglass")
}.tag(1)
NotificationView()
.onTapGesture {
self.selectedIndex = 2
}
.tabItem {
Image(systemName: "bell")
}.tag(2)
MessagesView()
.onTapGesture {
self.selectedIndex = 3
}
.tabItem {
Image(systemName: "envelope")
}.tag(3)
}
.navigationTitle(titleForIndex(selectedIndex))
.navigationBarTitleDisplayMode(.inline)
}
private func titleForIndex(_ index: Int) -> String {
switch index {
case 0: return "Home"
case 1: return "Explore"
case 2: return "Notifications"
case 3: return "Messages"
default: return ""
}
}
}
struct MainTabView_Previews: PreviewProvider {
static var previews: some View {
MainTabView()
}
}