추후에 제대로 정리할 예정.. 우선은 아무렇게나 기록 부터..
// 삼항 연산자 사용 예시
@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()
}
}