Lecture 14: Document Architecture

sun·2021년 11월 26일
0
post-thumbnail

유튜브 링크


# 앱 아이콘 추가하기

  • 오늘은 드디어 앱 아이콘을 추가해봤다! 아이콘 이미지 뭘로 하지 하다가 전에 금손 친구가 만들어준 미모지를 썼다. 교수님 말씀대로 인터넷에서 대충 iOS icon maker 로 검색해서 뜨는 사이트에서 사이즈별로 아이콘을 생성한 다음, 다운받은 폴더 그대로 Assets 에 넣어줬다. 그러고 나서 EmojiArt-General-App Icons and Launch Images 에서 source 를 해당 폴더로 바꿔주면 끝!

  • 엄청 간단한데 만족도가 생각보다 매우 높아서(xcode 상태바?에도 뜨고 시뮬레이터 돌릴 때도 괜히 귀여움...) 다음부터는 아이콘 설정부터 해줘야지 생각했다ㅋㅋㅋㅋㅋㅋ


# 기억해 xxxx년 xx월 xx일 xx시 xx분 xx초...내가 종료된 상태...

  • 유저들은 사실 앱을 켰을 때 마지막 종료 시(에러로 인한 강종 포함) 화면이 그대로 뜰 거라고 생각할텐데 아직 우리 앱은 종료하면 변경 사항은 저장되나 EmojiArtpalette 의 줌/팬 정돈나 순서 등이 초기 상태로 돌아간다. 따라서 @SceneStroage 를 써서 이러한 기능을 구현해줄 것이다.

  • @SceneStorageScene 별로 정보를 시스템에 저장하는데, 이 정보는 앱을 닫거나 강제종료해도 유지되므로, 다음에 다시 앱을 켰을 때 저장된 값이 있다면 이를 불러와서 해당 상태로 복구한다. 따라서 현재 우리가 원하는 복구 기능을 구현하는 데 적합한 친구!SceneStorage 에 변화가 발생하면 현재 View 가 무효화된다. 주의할 점은 저장과 복원은 시스템에서 모두 처리해주나, 저장할 수 있는 타입이 매우 한정되어 있다는 것.

  • 저장하려는 변수 앞에 @SceneStorage("someIdtoIdentifyThisVar") 와 같이 선언해주면 바로 앱 실행 시 자동 저장/복구가 가능하다! 이제 이모지 팔레트가 날씨-동물-음식 순이고 내가 음식 팔레트로 이동한 상태에서 앱을 껐다 다시 키면 날씨 팔레트가 아니라 음식 팔레트가 뜬다.

struct PaletteChooser: View {
    ...
    // required to identify this var in the SceneStorage
    @SceneStorage("chosenPaletteIndex")
    private var chosenPaletteIndex = 0
    ...
}
  • 다만 SceneStorage 가 기본으로 지원하지 않는 타입의 경우 SceneStorage 가 지원하는 RawRepresentable 로 변환해야 한다. 예컨대 우리의 경우 이모지 아트를 줌/팬 한 정도도 기억하고 싶을 수 있는데, 각각 CGFloat, CGSize 이므로 변환이 필요하다.

  • RawRepresentable 이란 raw value (우리가 아는 기본 타입들) 로의 변환/재변환이 가능한 타입이다. 따라서 이러한 변환 과정을 지정해주면 해당 프로토콜에 순응하게 할 수 있다! 우리는 String 타입으로 변환해줄 것이기 때문에 아래와 같이 RawRepresentableextension 을 추가해서 CGFloatCGSizeRawRepresentable 로 만들어주면, 이제 드디어 SceneStorageSteadyStateZoomOffset, SteadyStatePanOffset 에도 적용할 수 있다.

extension RawRepresentable where Self: Codable {
    public var rawValue: String {
        if let json = try? JSONEncoder().encode(self), let string = String(data: json, encoding: .utf8) {
            return string
        } else {
            return ""
        }
    }
    public init?(rawValue: String) {
        if let value = try? JSONDecoder().decode(Self.self, from: Data(rawValue.utf8)) {
            self = value
        } else {
            return nil
        }
    }
}

extension CGSize: RawRepresentable { }
extension CGFloat: RawRepresentable { }

# 시스템 폰트 크기 설정 일괄 적용하기

  • 디바이스 환경설정에서 폰트를 변경할 수 있는데, 문제는 우리가 별도로 .font(size:) 로 폰트를 지정한 View 들의 경우 디바이스에서 설정한 크기가 적용되지 않는다. 위에서 보면 이모지 팔레트 이름이나 메뉴는 시스템 설정에 따라 변경되었는데 defaultEmojiFontSize 를 지정해줬던 이모지들은 사이즈가 그대로다.

@ScaledMetric 을 붙이면 해당 변수의 크기를 유저의 시스템 설정 값과 일치하게 조정해준다!

struct EmojiArtDocumentView: View {
    ...
    @ScaledMetric var defaultEmojiFontSize: CGFloat = 40
    ...
}


# App

  • App 프로토콜 은 앱의 구조와 작동방식을 나타내며 한 앱당 오직 하나의 구조체만이 해당 프로토콜에 순응한다. 해당 구조체 앞에 @main 을 붙임으로써 Appentry point 임을 표시한다. App 프로토콜 은 앱을 실행하기 위해 시스템이 호출하는 main() 메서드를 내장하고 있다. AppbodyScene 인스턴스들로 구성된다.
@main
struct myApp: App {
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

# Scene

  • Scene 은 우리가 UI 상에 나타내려고 하는, View 체계에서 가장 상위에 있는 View 를 담고 있으며, 시스템에서 생명주기를 관리한다.

  • Scene 의 대표적인 3가지

    • WindowGroup { return aTopLevelView }
    • DocumentGroup(newDocument:) { config in ... return aTopLevelView }
    • DocumentGroup(viewing:) { config in ... return aTopLevelView}
  • Scenecontent 인자는 some View 를 리턴하는 클로저인데, 유저가 새로운 창을 띄울 때마다 호출되며, 새로운 Scene 의 최상위 View 를 리턴한다.

WindowGroup

  • non-document-oriented Scene-building Scene
  • 그룹의 content 로 선언한 View 구조가 해당 그룹으로부터 앱이 생성한 각 window 의 템플릿으로 사용된다. 각 window 는 독립적인 상태를 유지한다, 즉 새로운 Scene 의 각 View 구조 내부의 State 혹은 StateObject 변수들은 별도로 메모리에 저장된다.
  • 주의할 점은, 아래 코드에서 viewModelsharedInAllScenes 변수 의 경우 MyApp 에서 선언되어 inject 되고 있으므로 모든 Scene 이 공유한다.
@main
struct MyApp: App {
    @StateObject var viewModelharedInAllScenes = MyViewModel()
    var body: some Scene {
        WindowGroup {
            MyView(viewModel: viewModelSharedInAllScenes)  // Declare a view hierarchy here.
        }
    }
}

DocumentGroup

  • document-oriented Scene-building Scene
  • document Model 과 해당 document 타입을 나타낼 수 있는 View 를 사용해서 만든다. SwiftUI 는 모델을 사용해서 앱에서 해당 document 를 지원한다.
  • 새로운 document 가 생성되거나 열릴 때마다 SwiftUI 는 새로운 ViewModel 을 만든다.
  • config 인자에는 해당 documentViewModeldocument 를 여는 데 필요한 fileURL 이 들어있으며, 해당 URL 을 통해 ViewModel 에 접근해서 새로운 Scene 을 위한 View 를 만든다
    • e.g. EmojiArt 에서 config.documentEmojiArtDocument 타입!
  • newDocument 인자는 비어있는 새로운 document 를 만드는 데 사용하는 클로저
  • DocumentGroup 이 제대로 작동하기 위해서는
    • ViewModelReferenceFileDocument 프로토콜에 순응해야 하며
    • Undo 를 구현해야 한다 : Undo를 구현하는 대신 FileDocument 프로토콜에 순응하게 할 수도 있음 (슬라이드 참조)

A document is a body of information, such as pages of text, stored in a file locally or in iCloud. 출처: Apple Documentation

struct EmojiArtApp: app {
    var body: some Scene {
        DocumentGroup(newDocument: { EmojiArtDocument() }) { config in
            EmojiArtDocumentView(document: config.document)
        }
    }
}

# 커스텀 document type 만들기

  • document 기반의 앱을 만들고 있으므로 어떤 타입의 파일을 열고 수정할 수 있는지 표시해야 하는데, 특히 우리처럼 custom document 를 사용하는 경우 새로운 타입을 선언해야 한다. 따라서 Uniform Type Identifiers 를 사용할 건데, extension 을 통해 커스텀 타입을 추가할 수 있다.

    • Uniform Type Identifiers : 파일 혹은 데이터의 타입을 식별하는 데 사용되는 기본 타입들을 제공한다.
  • Xcode는 앱을 빌드할 때, document 타입에 관한 정보를 앱의 information property list(이하 Info.plist) 파일에 넣고, 유저들이 앱을 설치하면 시스템에서 해당 정보를 사용해 앱이 열 수 있는 파일들을 결정한다. 따라서 Info.plist 에서 타입을 선언하면 된다.

  • EmojiArt - Targets: EmojiArt - Info 에서 Exported Types Identifiers, Imported Type Identifiers, Document Types 를 셋팅해주면 된다. 이하는 공식문서와 강의를 참고해서 정리했다.

  1. 타입을 선언할 때는 먼저 UTType-열거나, 보내거나, 받을 데이터의 타입을 나타내는 구조체-을 선언한다. 즉, 우리의 앱이 source 가 되는 exported type 인지, 아니면 외부에서 선언된 타입을 사용하는 imported type 인지 선언한다. 커스텀 document 타입을 사용하는 경우 exported typeimported type 을 모두 선언해줘야 한다. 우리의 경우 emojiart 라는 커스텀 타입을 사용할 것이고 파일을 열기도하고(import) 저장하거나 내보내기(export)도 할 것이므로 둘 다 선언해 준다.

    • 고유한 identifier 가 필요하기 때문에 edu.stanford.cs193p 와 같은 reverse DNS 형식으로 선언해준다.
    • type conformance 도 선언할 수 있는데, document type 을 선언할 때는 아래의 두 identifier 에 반드시 순응해야 한다.
      • public.data : Finder 혹은 Files app 이 해당 document type 을 표시할 수 있도록 함 (i.e. 저장된 아이템이 해당 타입인지 식별/표시)
      • public.content : 유저들이 에어드랍을 통해 해당 타입을 공유할 수 있게 함
    • .jpeg 와 같이 해당 타입을 나타낼 확장자도 지정할 수 있다! 우리의 경우 emojiart 로 지정하면 이제 모든 파일에 .emojiart 가 붙는다.
  2. UTType 을 선언한 뒤에는 위에서 선언한 두 UTI 가 우리의 앱이 소유하는 document 를 나타낸다는 것을 알려주는 Document Type 을 선언해야 한다

    • 우리는 단순히 Viewer 가 아니고 emojiart 를 편집할 수 있으므로 Handler RankOwner 로 설정한다

# ViewModel과 ReferenceFileDocument protocol

  • Info.plist 뿐만 아니라 코드에도 우리가 읽고 쓸 수 있는 UTTypes 들을 선언해줘야 한다.

  • 먼저 우리가 Info.plist 에 선언한 타입을 extension 을 사용해서 UTTypestatic 프로퍼티로 선언해주면 해당 타입을 의미하는 UTType.emojiart 가 새로 정의된다. 이 친구를 이제 아래에서 만나볼 ReferenceFileDocument 프로토콜에 순응하도록 구현하는 과정에서 쓴다.

extension UTType {
    static let emojiart = UTType(exportedAs: "edu.stanford.cs193p.emojiart")
}

ReferenceFileDocument

  • 파일(디스크)로부터/에 EmojiArtDocument 를 읽어오기/쓰기 위해 필요한 프로토콜로 이전에 우리가 직접 작성했던 autosaving 관련 코드를 모두 대체한다.

    • 원리는 Model 에 변화가 발생하면 백그라운드 스레드에서 snapshot(contentType:) 이 호출되고, snapshot 을 담을 수 있는 filewrapper 가 요청된다.
  • ObservableObject 프로토콜에 순응하므로 ViewModel 에만 적용할 수 있으며, snapshot 을 이용해 쓰기 작업을 백그라운드 스레드에서 수행한다.
    - cf. FileDocument : Model 을 디스크에/로부터 저장하고/불러오는 데 필요한 프로토콜

  • readableContentTypes : document 가 열 수 있는 타입들

  • writeableContentTypes : document 가 저장하거나 변환될 수 있는 타입

  • fileWrapper(snapshot:configuration:) : snapshot 을 직렬화해서 파일 시스템에 저장하며, 저장 위치를 반환한다. 보통은 데이터 blob 에 불과하지만 더 복잡한 데이터를 담을 수도 있다.
    - file wrappers : 파일 시스템 상의 노드(파일, 디렉토리, 심볼릭 링크)를 나타내는 객체로 FileWrapper 클래스의 인스턴스. 종류에 따라 노드명, 컨텐츠, 도착 노드 등에 대한 정보를 담고 있다

  • snapshot(contentType:) : document 의 현재 상태를 담은 Snapshot 을 생성해서 리턴한다. 이때 Snapshot 은 제너릭으로 보통 Data 타입으로, 직렬화에 사용된다.
    - Snapshot 이 생성될 때까지 document 는 수정 불가하며, Snapshot 이 생성되면, document 는 다시 수정 가능해지고 Snapshotwrite(snapshot:to:contentType:) 메서드를 이용해 직렬화한다.

  • init(configuration:) : 주어진 ReadConfiguration 에 들어있는 정보( UTType, file wrapper )를 불러와서 self 를 초기화한다.
    - ReadConfiguration , WriteConfiguration : 어디서/에 document 를 불러오고 저장할 지 알려준다.


EmojiArtDocument가 ReferenceFileDocument 프로토콜 따르게 하기

  • 먼저 우리가 불러오고 저장할 타입이 아까 위에서 지정한 UTType.emojiart 라고 선언해준다.

  • 그리고 우리는 Snapshot 의 타입을 Data 로 선언해줄 거다!

  • snapshot(contentType:)EmojiArtModelData 로 변환한 값을 리턴하므로 ModelData 로 바꿔줘야 한다. EMojiArtModeljson 메서드를 사용해 모델을 Data 형태로 인코딩한다.

  • fileWrapper(snapshot:configuration:) 함수의 경우 FileWrapper 를 리턴해야 하는데, FileWrapper(regularFileWithContents:) 의 인자로 snapshot 을 넣어주면 해당 Data 를 담고 있는 regular-file file wrapper 를 생성한다.

  • required init(configuration:) 에서는 configuration 에 있는filewrapper 에 저장되어 있는 contents 를 불러온 다음 역직렬화해서 emojiArt 로 설정한다. Model 을 불러온 다음에는 배경 이미지도 꼭 불러와주기...! 실패하는 경우 에러를 던지는데 보통은 그럴 일이 없을 거라고 하셨다....

class EmojiArtDocument: ReferenceFileDocument{
    
    // MARK: - ReferenceFileDocument
    
    static var readableContentTypes = [UTType.emojiart]
    static var writeableContentTypes = [UTType.emojiart]
    
    required init(configuration: ReadConfiguration) throws {
        if let data = configuration.file.regularFileContents {
            emojiArt = try EmojiArtModel(json: data)
            fetchBackgroundImageDataIfNecessary()
        } else {
            throw CocoaError(.fileReadCorruptFile)
        }
    }
    
    func snapshot(contentType: UTType) throws -> Data {
        // Model converted (possibly) to some other type, like a Data 
        try emojiArt.json()
    }
    
    // writing the document out to a file
    func fileWrapper(snapshot: Data, configuration: WriteConfiguration) throws -> FileWrapper {
        FileWrapper(regularFileWithContents: snapshot)
    }
    ...
}

# Undo를 할 수 있다는 것...? 저장할 일이 생겼다는 것...!

  • 아까 ReferenceFileDocument 프로토콜에 따르게 하면 자동저장이 된다고 했는데, 새 파일을 생성해서 이모지를 추가한 다음 껐다 키면 다시 빈 파일이 우리를 맞이한다...아직 Undo 를 구현하지 않았기 때문인데, SwiftUIundo 가 등록될 때? how do i say register...변화가 생겼음을 인식하고, 그제서야 snapshot(contentType:) 을 호출한다.

  • Undo 구현은 undoManager 를 사용해서 하는데, 특정 작업을 수행할 때 해당 작업에 대한 undo operationundo stack 에 넣으면 해당 작업을 undoable 하게 만들 수 있다. 스택에 넣은 뒤에 undo() 메서드를 호출하면 undo 작업이 수행된다.

    • UndoManagerregisterUndo(withTarget:handler:) 메서드를 사용하면 undo stackhandler 클로저target 에 대한 undo operation 으로 넣을 수 있고, undo() 호출 시 해당 클로저가 실행된다.

    • undo managerundo/redo 작업을 수행할 수 있도록 targetreference type 으로 전달되어야 하며, ARC 를 피하기 위해서 unowned reference 를 갖는다.
  • 주로 Intent 함수 들을 호출할 때 유저가 undo 하고 싶을 것이므로 보통 ViewModel 내 관련 코드가 위치하는데, Modelvalue type 인 경우 ( handler 에서 쓰일) snapshot 을 만들기 쉬워 쉽게 undo 할 수 있다

  • 그러나 UndoManager 자체는 ViewEnvironment 의 일부이므로, Intent 함수 가 호출될 때 View 로부터 넘겨받아야 한다.

undoablyPerform(operation:with:doit)

  • undo 작업을 좀 더 쉽게 하기 위해 만든 함수인데 기본 메커니즘은 undoable 하게 만들고 싶은 작업인 doit 을 수행하기 전에 Model 을 로컬 변수에 저장한 다음(스냅샷을 저장), doit 을 수행하면서, undoManager 를 사용해서 undo operation 을 등록하는 것. 이때, undo operation 은 현재 Model 을 아까 로컬 변수에 저장한 Model 로 바꿔주는 클로저다.
    • 여기서 undo operationundoablyPeform 하면 undoundo 하는 것이므로 redo 가 가능해진다!
    • operation 인자는 나중에 View 에서 menu item 이 어떤 작업을 undo 할지 표시하는 데 쓰기 위해서 받는 String
class EmojiArtDocument: ReferenceFileDocument {
    ... 
    private func undoablyPerform(operation: String, with undoManager: UndoManager? = nil, doit: () -> Void) {
        let oldEmojiArt = emojiArt  // snapshot our Model so we can undo back to it
        doit()  // do some operation 
        // make it undoable 
        undoManager?.registerUndo(withTarget: self) { myself in
            // made it redo by making undo undoable...
            myself.undoablyPerform(operation: operation, with: undoManager) {
                myself.emojiArt = oldEmojiArt
            }
        }
        undoManager?.setActionName(operation)
    }
}
  • 우리는 Intent 함수 들의 작업을 undo 하려고 하는 것이므로 각 Intent 함수 내부에서 작업을 수행하는 코드를 undoablyPerform(operation:with:doit:)doit 인자로 넣어주면 해당 작업이 undoable 해진다!
class EmojiArtDocument: ReferenceFileDocument {
    func setBackground(_ background: EmojiArtModel.Background, undoManager: UndoManager?) {
        undoablyPerform(operation: "Set Background", with: undoManager) {
            emojiArt.background = background
        }
    }
    
    func addEmoji(_ emoji: String, at location: (x: Int, y: Int), size: CGFloat, undoManager: UndoManager?) {
        undoablyPerform(operation: "Add \(emoji)", with: undoManager) {
            emojiArt.addEmoji(emoji, at: location, size: Int(size))
        }
    }
    
    func moveEmoji(_ emoji: EmojiArtModel.Emoji, by offset: CGSize, undoManager: UndoManager?) {
        if let index = emojiArt.emojis.index(matching: emoji) {
            undoablyPerform(operation: "Move", with: undoManager) {
                emojiArt.emojis[index].x += Int(offset.width)
                emojiArt.emojis[index].y += Int(offset.height)
            }
        }
    }
    
    func scaleEmoji(_ emoji: EmojiArtModel.Emoji, by scale: CGFloat, undoManager: UndoManager?) {
        if let index = emojiArt.emojis.index(matching: emoji) {
            undoablyPerform(operation: "Scale", with: undoManager) {
                emojiArt.emojis[index].size = Int((CGFloat(emojiArt.emojis[index].size) * scale).rounded(.toNearestOrAwayFromZero))
            }
        }
    }
}

@Environment(.undoManager)

  • 위에서 얘기했듯이, undoManagerViewEnvironment Value 에 해당되므로 View 에서 intent 함수 를 호출할 때 인자로 넣어줘야 한다. 따라서 아래와 같이 undoManager 를 선언해서 쓴다!
struct EmojiArtDocumentView: View {
    ...
    @Environment(\.undoManager) var undoManager
    ...
}

# undo/redo 버튼 만들기

  • undo/redo 를 구현했으니, 유저가 실제로 해당 작업을 할 수 있도록 Viewtoolbarundo/redo 버튼을 달아주면 이제 진짜 끝이다..다음 extensionView 를 사용했는데, UndoManager extension 은 만약 undo/redo 할 작업이 있는 경우 해당 작업의 이름이나 디폴트값을 리턴하도록 하고, UndoButton 은 작업의 이름을 인자로 받아서 undo/redo 할 작업이 있는 경우에만 버튼의 라벨로 써서 버튼을 나타낸다.
struct UndoButton: View {
    let undo: String?
    let redo: String?
    
    @Environment(\.undoManager) var undoManager
    
    var body: some View {
        let canUndo = undoManager?.canUndo ?? false
        let canRedo = undoManager?.canRedo ?? false
        if canUndo || canRedo {
            Button {
                if canUndo {
                    undoManager?.undo()
                } else {
                    undoManager?.redo()
                }
            } label: {
                if canUndo {
                    Image(systemName: "arrow.uturn.backward.circle")
                } else {
                    Image(systemName: "arrow.uturn.forward.circle")
                }
            }
            .contextMenu {
                if canUndo {
                    Button {
                        undoManager?.undo()
                    } label: {
                        Label(undo ?? "Undo", systemImage: "arrow.uturn.backward")
                    }
                }
                if canRedo {
                    Button {
                        undoManager?.redo()
                    } label: {
                        Label(redo ?? "Redo", systemImage: "arrow.uturn.forward")
                    }
                }
            }
        }
    }
}

extension UndoManager {
    var optionalUndoMenuItemTitle: String? {
        canUndo ? undoMenuItemTitle : nil
    }
    var optionalRedoMenuItemTitle: String? {
        canRedo ? redoMenuItemTitle : nil
    }
}
struct EmojiArtDocumentView: View {
    ...
    @Environment(\.undoManager) var undoManager

    var documentBody: some View {
        GeometryReader { geometry in
            ZStack {
                // some code
            }
            .toolbar {
                UndoButton(
                    undo: undoManager?.optionalUndoMenuItemTitle,
                    redo: undoManager?.optionalRedoMenuItemTitle
                )
            }
        }
    }
    ...
}

# system file에서 해당 타입 열 수 있게 하기

  • EmojiArt 랑 비슷해 보이지만 아이패드의 Files 를 이용해서 저장된 파일들을 본 건데, Sun.emojiart 를 누르면 emojiart 가 뜨는 게 아니고 그냥 설명만 뜬다

  • iOS에 시스템 Files app 에서 .emojiart document 들을 열어도 된다고 알려줘야 하는데 Info.plist 에서 우클릭 - Add Row - Supports Document BrowserYes 로 설정해주면 된다! 이렇게 하면 시스템 Files 앱 에서 .emojiart 파일들을 눌렀을 때 파일이 열리면서 EmojiArt 앱으로 연결된다.


☀️ 느낀점

  • 사실 새 프로젝트를 생성할 때마다 자동으로 생성되는 App 파일을 들여다보지 않은 게 걸려서 완강하고 나면 꼭 공부해봐야지 했는데 역시...교수님은 계획이 다 있으셨다...초반에는 배울 게 너무 많아서 App 파일을 보고도 아 미래의 나에게 맡기자라고 생각했는데 슬슬 Swift 이제 좀 익숙해졌다 싶어서 아 이것도 이제 진짜 봐야지 싶을 때 딱 강의 주제로 나오다니 소름...ㅋㅋㅋㅋㅋㅋㅋ

  • Document-oriented App 이란 무엇인가에 대해서 감이 잘 오지 않았는데 이번 강의를 들으면서 일반 앱과의 차이를 알 수 있었다.







App Architecture

  • SceneStorage : state restoration, essentially b/c ur gonna restore the state of ur applicatoin from when u had quit or killed
  • AppStorage : kinda an interface to UseDefaults, but does invalidate ur View when u change it
  • ScaledMetric: lets u modify fonts that have been set with custom fonts for their own

SwiftUI has the whole thing that helps enhance accessiblity..try to look it up later

Document Architecture

  • each of the Scenes(i.e. the top level View) in the documentGroup is using its own VM

  • protocol ReferenceFileDocument : it's all about putting ur document onto disc and getting it off the disc

  • Undo: the way that the document architecture knows that ur documnet has changed and can auto-save it
    - the whole saving mechanism in iOS is based on auto-saving ur documnet(unlike Mac, there's no save menu)

  • FileDocument: sth that the Model must conform to if we don't want to implement Undo...it's about put the model in and out of disc
    - init : gives ReadConfiguration which tells u the file from which u r suppose to read ur document

    • fileWrapper : asks u to give it a FileWrapper that contains urself encoded somehow for the file system. filewrapper is usually just storing a blob of data in there but it can do more sophisticated storage of complicated files
    • used with Value types
  • ReferenceFileDocument
    - for reference types...

    • fileWrapper method now requires and return a snapshot

Undo

  • it's easy to Undo in ur ViewModel if ur Model is a struct b/c value types can be copied around easily

making UTTypes : 45분 전후

  • need to fill things to desribe to the system what our app type is
  • define exported/imported type identifiers
  • Document type : tell exported/imported type identifiers represent a document that our application owns

Redo Undo : 1:05분

.jpg? .emojiart!

  • make type EmojiArtDocument...

  • mark Document Type as owner b/c we're not just a viewer, we actually own this type, so we're the ones who r gonna be registering it on the iOS device and all that

  • make reference blahblah protocol

  • undo/redo

profile
☀️

0개의 댓글

관련 채용 정보