https://lsh424.tistory.com/83#recentComments

  • 모든 플랙폼에서 앱의 인터페이스와 동작을 선언
  • 선언형 프로그래밍 프레임워크

App : App

  • uiKit에서의 appdelegate로 생명주기나 uiapplication 객체에 대한 컨트롤을 하는 역할
  • 앱의 구조와 동작을 나타내기 위한 타입
  • 말 그대로 “앱” 을 구성하고 동작시키기 위해 있는 타입(프로토콜)

    → 사용할 땐, 구조체에 해당 프로토콜을 채택

    그리고 body는 반드시 있어야 하는 속성

    그리고 @main 을 표시

@main
struct Mail: App {
    var body: some Scene {
        WindowGroup {
            MailViewer()
        }
        Settings {
            SettingsView()
        }
    }
}

ContentView : View

  • 두가지 구조체 선언
    • view의 컨텐츠와 레이아웃을 묘사
    • 해당 뷰에 대한 preview를 선언
  • command 버튼을 누른 상태에서 preview를 누르면 수정 가능
  • view에 있는 body는 연산 프로퍼티라고 볼 수 있음

      struct BeatifulView: View {
      	var body: some View {
          Text("Hello SwiftUI")
      	}
      }
    
  • view를 커스터마이징하기 위해서는 modifer 메소드를 사용
    • ex) .font(.body) .foregroundColor(.green)
  • 스택의 종류
    • HStack : 수평스택
    • VStack : 수직스택
    • ZStack : z축으로 뷰를 쌓는 스택
    • LazyVStack : 사용자가 보는 화면에 무엇을 랜더링할 필요가 있을 때까지는 구성하지 않는다는 뜻

        ScrollView {
        	LazyVStack(alignment: .leading) {
        		ForEach(1...10, id: \.self) {
        			Text("Row \($0)")
        		}
        	}
        }
      

https://seons-dev.tistory.com/category/SWIFTUI/Grammar 참조

Uikit VS SwiftUI

스크린샷 2022-10-11 오후 3.06.08.png

스크린샷 2022-10-11 오후 3.06.16.png

@State

  • swiftUI에서 관리하는 값을 읽고 쓸 수 있는 속성 래퍼 유형
  • 사용자의 Input에 따라 바뀌는 변수
  • state값이 변경될 때마다 항상 새로운 view에서 렌더링된다
  • @state는 struct에서만 작동!
  • view의 Body에서만 state변수에 접근해야 함
  • private로 둬야 안전
struct UserSettings {
    var score = 0
}

struct ContentView: View {

    @State var settings = UserSettings()

    var body: some View {
        VStack {
            Text("나의 점수는 \(settings.score)점 입니다.")
            Button(action: {
                self.settings.score += 1
            }) {
                Text("Increase Score")
            }
        }
    }
}

→ 약간 uikit의 didset과 동일하지만 차이점은 didset은 내부코드블럭만 실행된다면 state는 view를 아예 다시 만든다!!!

didset은 클래스 내부에서 사용하고 state는 struct에서 사용한다는 차이점이 존재, struct에서 값이 변경가능하고 값이 변경될 때마다 view를 다시 그리고 싶어서 state라는 개념을 도입한 걸로 생각하면 된다!@!

@Binding

상위 view : @state (전달해줄거면)

-state를 전달해줄거라면 $를 붙여서 전달

하위 view : @Binding (전달 받을 예정이면)

import SwiftUI

struct Person: Identifiable {
    let id = UUID()
    var fristName: String // 데이터가 변경될 예정이므로 var
    var lastName: String  // 데이터가 변경될 예정이므로 var
    
    init(_ fristName: String, _ lastName: String) {
        self.fristName = fristName
        self.lastName = lastName
    }
}

struct ContentView: View {
    //detail view로 보낼 값
    @State private var people: [Person] = [
        Person("Uno", "Kim"),
        Person("Uno", "Park"),
        Person("Uno", "Lee"),
    ]
    
    var body: some View {
        
       NavigationView {
            VStack {
                HeaderView("Hello, SwiftUI",
                           "by Uno")
                
                List($people) { $person in
                    NavigationLink(destination: {
                        DetailView(
                            fristName: $person.fristName,
                            lastName: $person.lastName)
                    }, label: {
                        Text("\(person.lastName) \(person.fristName)")
                    })
                }
                Spacer()
            }
            .navigationTitle("Binding과 List의 만남")
        }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct HeaderView: View {
    let title: String
    let subtitle: String
    
    init(_ title: String,
         _ subtitle: String) {
        self.title = title
        self.subtitle = subtitle
    }
    
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text(title)
                    .font(.system(size: 20, weight: .bold, design: .monospaced))
                    .lineLimit(1)
                
                Text(subtitle)
                    .font(.system(size: 11, weight: .medium, design: .monospaced))
                    .lineLimit(1)
                    .foregroundColor(.gray)
                    .padding(.leading, 3)
            }
            .padding(.leading, 18)
            Spacer()
        }
    }
}
import SwiftUI

struct DetailView: View {
    @Binding var fristName: String
    @Binding var lastName: String

    var body: some View {
        HStack {
            VStack {
                Text("성과 이름을 수정하세요.")
                
                Color.black
                    .frame(
                        width: UIScreen.main.bounds.width,
                        height: 1)
                
                TextField(fristName, text: $fristName)
                
                Divider()
                
                TextField(lastName, text: $lastName)
            }
            .padding(10)
            .border(.black, width: 1)
            
            Spacer()
        }
        .navigationTitle("Editing Name")
    }
}

struct DetailView_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView {
            DetailView(fristName: .constant("Uno"), lastName: .constant("Kim"))
        }
    }
}

@ObservedObject

  • 간단한 로컬 프로퍼티(String, integer) 대신 외부 참조 타입을 사용
  • state와 비슷
  • class를 사용하고 싶다면 state가 아니라 이걸 써야 함!
  • @observedObject가 데이터가 변경되었음을 view에 알리는 방법은 @Published 프로퍼티를 사용하는 것
  • 주로 viewModel을 선언할 때 사용하는 프로퍼티
  • observableObject를 구독하고 값이 업데이트될 때마다 뷰를 갱신하는 propertyWrapper
class UserSettings: ObservableObject {
   //@ObervedObjet를 사용하기위해 @Published를 할당
   @Published var score = 0
}

struct ContentView: View {
   //@state를 지우고 @ObervedObject로 바꿔줌
    @ObservedObject var settings = UserSettings()

    var body: some View {
        VStack {
            Text("나의 점수는 \(settings.score)점 입니다.")
            Button(action: {
                self.settings.score += 1
            }) {
                Text("Increase Score")
            }
        }
    }
}

→ score이 변경되면 view를 reload하게 된다

@StateObject

  • observedObject와 동일하지만 ios14에서 단점을 보완해서 나온 개념
  • 다른뷰는 영향을 받지 않고 observable이 가능하다는 장점이 존재
class TestObject: ObservableObject {
    @Published var num: Int = 0
}

struct StateObjectTestView: View {
    @StateObject var stateObject = TestObject()
    
    var body: some View {
        VStack {
            Text("State object: \(stateObject.num)")
            Button("Increase state object", action: {
                stateObject.num += 1
                print("State object: \(stateObject.num)")
            })
        }
        .onChange(of: stateObject.num) { newStateObject in
            print("State: \(newStateObject)")
        }
    }
}

@Published == observable

  • 변경이 발생하면 자동으로 알려주는 observable객체들을 만들 수 있다
  • ObservableObject에서 property를 선언할 때 사용하는 PropertyWrapper
class Bag: ObservableObject {
    @Published var items = [String]()
}

@EnvironmentObject

  • 상위 뷰에서 제공하는 observableObject에 대한 propertyWrapperType
  • 상위뷰에서 정의한 프로퍼티를 하위뷰에서도 변경할 수 있도록 하는..뭐그런거…
  • preview에서 .encironmentObject()로 전달해주면 되는 거 같은…느낌인데 먼지 모르겠어….

Binding

@State var name: String = ""
    
    var body: some View {
        VStack {
            Text("Your name is \(name)")
            TextField("이름", text : $name)
        }
    }
struct Epicode{
    let song : String
    let singer : String
    let track : String
}

struct BIndingPractice: View {
    
    let epicode = Epicode(song: "strawberry Moon", singer: "IU", track: "Celebrity 1th")
    
    @State private var isPlaying = false
    
    var body: some View {
        VStack{
            Image("chincoteague")
                .cornerRadius(10.0)
            
            Text(epicode.song)
                .font(.title)
                .foregroundColor(self.isPlaying ? .blue : .black)
            Text(epicode.singer)
                .foregroundColor(.secondary)
            Text(epicode.track)
                .font(.footnote)
                .foregroundColor(.secondary)
            
            PlayButton(isPlaying: $isPlaying)
        }
    }
}

struct PlayButton: View {
    @Binding var isPlaying : Bool
    
    var body: some View {
        
        Button(action: {
            self.isPlaying.toggle()
        }) {
            Image(systemName: "play.fill")
            .font(.system(size: 30))
            .foregroundColor(self.isPlaying ? .blue : .black)
        }.padding(15)
    }
}

struct BIndingPractice_Previews: PreviewProvider {
    static var previews: some View {
        BIndingPractice()
    }
}

TabView

struct ContentView: View {
    @State private var selection: Tab = .featured
    
    enum Tab { //tab 종류
          case featured
          case list
      }
    
    var body: some View {
	//여기서 tabView를 만듦
        TabView(selection: $selection) {
           CategoryHome()
                .tabItem {
                   Label("Featured", systemImage: "star")
               }
               .tag(Tab.featured)

           LandmarkList()
                .tabItem {
                   Label("List", systemImage: "list.bullet")
               }
               .tag(Tab.list)
       }
    }
}
  • 뷰의 특정 속성으로 되어 있는 것을 변경하고 싶다?? → environment를 떠올리면 돼!!

ForEach

  • ForEach를 뷰를 제공할 때, 사용합니다. 그런데 그 뷰는 “RandomAccessCollection”의 데이터 타입인 뷰 입니다.
  • 컬렉션의 구성요소들은 Identifiable  을 준수하고 파라미터로 id값을 반드시 주어야 합니다.
  • tableview, collectionView 사용에 주로 많이 사용

문제점! : swiftUI를 배우다보니 드는 의문점이 MVVM이랑 맞냐라는 생각이 들었다… 음 viewModel에는 로직과 뷰를 분리한다는 장점도 있지만 무엇보다 데이터바인딩이라는 장점이 있는데 swiftUI는 이 기능이 view에 있어서 이걸 뭐라고 해야하나…. ㅠㅠㅠ 애매하당 뭐가 나은거징?

  • swiftUI에는 info.plist가 없고 target에 들어가서 setting에서 바꿔야 함
  • swiftUI에는 rxswift 대신 combine를 쓴다고 한다 이것도 배워야하는 건가..? 세상에
    • rxswift과 거의 비슷하지만 가볍지만 대신 disposeBag이 없다고 한다
  • https://eunjin3786.tistory.com/67

SwiftUI에서의 아키텍처

model - view

스크린샷 2022-10-13 오후 1.36.11.png

struct ChatListView: View {
    let chatService: ChatService

    var body: some View {
        NavigationView {
            List(chatService.fetchChats()) { chat in
                NavigationLink(
                    destination: ChatDetailView(chat: chat, chatService: self.chatService)
                ) {
                    ChatCell(chat: chat)
                }
            }
            .navigationBarTitle("Chats")
        }
    }
}
struct ChatDetailView: View {

    private let chat: Chat
    private let chatService: ChatService
    
    @ObservedObject private var keyboardObserver = KeyboardObserver.shared
    
    @State private var newMessage = ""
    @State private var messages: [Message] = []

    /* ... */

    var body: some View {
        VStack {
            List(messages) { message in
                MessageView(message: message,
                            isMine: self.chatService.currentUser == message.sender)
            }

            Divider()

            HStack {
                TextField("New message",
                          text: $newMessage,
                          onCommit: sendMessage)
                Button(action: sendMessage) {
                    Text("Send")
                }
            }
            .padding([.leading, .top, .trailing])
        }
        .padding(.bottom, keyboardObserver.height)
        .navigationBarTitle(Text(chat.title), displayMode: .inline)
        .animation(.easeInOut)
        .onAppear { self.reloadMessages() }
    }

    private func sendMessage() {
        chatService.addMessage(newMessage, to: chat)
        newMessage = ""
        reloadMessages()
    }

    private func reloadMessages() {
        self.messages = chatService.fetchMessages().filter { $0.chatId == chat.id }
    }

}
  • 장점
    • 빠르게 개발 가능
    • 실제로 변경된 경우에만 업데이트되므로 적게 업데이트된다
  • 단점
    • 복잡해지면 보기가 어려워질 수 있다
    • 재사용성이 낮다

MVVM

  • 기존의 uikit에서의 mvvm과 동일
import SwiftUI

struct ContentView: View {
    
    @ObservedObject private var counterVM: CounterViewModel
    
    init() {
        counterVM = CounterViewModel()
    }
    
    var body: some View {
        VStack {
            
            Text(counterVM.premium ? "PREMIUM" : "")
                .foregroundColor(Color.green)
                .frame(width: 200, height: 100)
                .font(.largeTitle)
            
            Text("\(counterVM.value)")
                .font(.title)
            Button("Increment") {
                self.counterVM.increment()
            }
        }
    }
}
import Foundation
import SwiftUI

class CounterViewModel: ObservableObject {
    
    @Published private var counter: Counter = Counter()
    
    var value: Int {
        counter.value
    }
    
    var premium: Bool {
        counter.isPremium
    }
    
    func increment() {
        counter.increment()
    }
}

Redux

스크린샷 2022-10-13 오후 1.38.17.png

Flux

리엑트에서 주로 사용하는 아키텍처

스크린샷 2022-10-13 오후 1.46.23.png

  • action이 발생하면 dispatcher에 의해 store에 변경된 사항이 저장되고 저장된 사항에 따라서 view가 변경되는 단방향패턴
  • dispatcher는 action을 정리해주고 store은 어플리케이션의 데이터들이 저장되는 장소
  • mvc의 구조의 단점을 보완하는 단방향 데이터 흐름 구조

    Untitled

  • 장점 : 단방향으로 흐르기 때문에 훨씬 파악하기가 쉽고 코드의 흐름이 예측가능

Redux

리엑트에서 주로 사용하는 아키텍처

  • Flux와 달리 리덕스는 dispatcher라는 개념이 존재하지 않는다.
  • 리덕스는 다수의 store도 존재하지 않는다. 대신 리덕스는 하나의 root에 하나의 store만이 존재한다.
  • 순수함수(pure functions)에 의존한다. (state의 불변성)

스크린샷 2022-10-13 오후 3.06.38.png

Untitled


SwiftUI와 Combine

  • Combine은 시간의 흐름에 따라 값을 처리하기 위한 Declarative Swift API를 제공하는 프레임워크인 것이다.

Untitled

Publisher

  • 게시자
class IntPublisher: Publisher {

    typealias Output = Int
    typealias Failure = Never

    func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {

    }
}
  • output : 어떤 타입의 데이터 스트림을 보낼 수 있는지를 나타냄
  • failure : 실패할 경우 어떤 타입을 보낼 것인지
  • 해당 publisher를 구독하는 subsciber는 input, failure가 publisher의 output, failure와 일치해야 함
  • receive : 해당 publisher를 구독하는 subsriber를 등록할 때 호출되는 메소드

Subscriber

  • publisher를 구독하는 객체 subscriber가 publisher가 발행하는 데이터 스트림을 받아 처리할 수 있다
class IntSubscriber: Subscriber {
    typealias Input = Int
    typealias Failure = Never

    func receive(subscription: Subscription) {

    }

    func receive(_ input: Int) -> Subscribers.Demand {
        return .unlimited
    }

    func receive(completion: Subscribers.Completion<Never>) {

    }
}
  • input : 어떤 타입의 데이터 스트림을 받을 수 있는지를 나타냄
  • failure : 실패할 경우 어떤 타입의 형태로 처리되는지
  • receive(sub -) : publisher에 대한 구독이 성공했을 때 subscription이라는 객체를 전달받는데 이 때 호출되는 메소드
  • receive(input) : 구독중인 publisher에 대하여 value를 전달 받았을 때 호출되는 메소드
  • receive(completon) : 구독중인 Publisher로부터 completion을 전달 받았을 때 호출되는 메소드

Subscription

  • 구독을 대변하는 객체
  • publisher와 subscriber를 이어줌
class TestSubscription: Subscription {
    func request(_ demand: Subscribers.Demand) {

    }

    func cancel() {

    }
}
  • request : subscriver가 얼마나 데이터를 전달받을지를 결정
  • cancel : 구독을 취소

combine 객체들의 상호작용

Untitled

  1. 먼저 Subscriber 객체가 Publisher에 구독을 시작한다. (Subscriber의 receive<S>(subscriber: S) 메서드 실행)
  2. Publisher가 Subscription 객체를 생성한다.
  3. Publisher가 이를 Subscriber 객체에게 전달한다. (Subscriber의 receive(subscription: ) 메서드 실행)
  4. Subscriber가 Subscription에게 values를 요청한다.
  5. Subscription이 values를 가져온다.
  6. Subscription이 Subscriber에게 values를 전달한다. (Subscriber의 receive(_ input: ) 메서드 실행)
  7. Subscription이 Subscriber에게 completion을 전달한다. (Subscriber의 receive(completion: ) 메서드 실행)