Closed30

PoPのCanvasまわりのロジックを改善する

とりあえずSwiftUIのプレビューを整備する

デバイスを定義
xcrun simctl list devicetypes でシミュレーターの文字列一覧が出せる

enum TargetPreviewDevice: String, Identifiable, CaseIterable {
    var id: String { rawValue }

    case iPhone13Pro = "iPhone 13 Pro"
    case iPadPro5th = "iPad Pro (12.9-inch) (5th generation)"
}


// MARK: - PreviewProvider

struct SideBarList_Previews: PreviewProvider {
    static var previews: some View {
        ForEach(TargetPreviewDevice.allCases) { deviceName in
            SideBarList()
                .previewDevice(PreviewDevice(rawValue: deviceName.rawValue))
                .previewDisplayName(deviceName.rawValue)
        }
    }
}

こんな感じで

APIとの通信があるところは、テストデータを差しこまないといけないんだけど、ちょっと困ってる
SwiftUIじゃなければ、ViewModelにprotocol設定して終わりなんだけど、
SwiftUIだとジェネリクス使うなり、一手間いるみたい

struct ItemView<Model>: View where Model: ItemViewModel {
    @ObservedObject var viewModel: Model
// …
}

うーんこれはちょっと嫌な感じがする

https://stackoverflow.com/questions/59503399/how-to-define-a-protocol-as-a-type-for-a-observedobject-property

Canvasまわりのコメントアウトしてたpreviewをとりあえず整備した
NoteInformationNoteDocument をテストデータにすると、previewができなくなる
これがよくわからない……あまり意味はないけど、とりあえずnilにして、データがない状態でプレビューしとく

よく見てみると、NoteDocument 引数にしてるコンポーネントは軒並みpreviewダメだったんだな

https://github.com/0si43/PiecesOfPaper/issues/116

このissueの原因わかった。
initialContentSize() の中で、WidthかHeightだけ入れるとダメなんだ
例えばこんな絵

これはHeightだけウィンドウサイズを超過するので、contentSizeをHeightだけ入れるロジックになる。
これだとWidthが0のままなので、コンテントの大きさを0として扱うっぽい

パターンを整理しよう。

  • 比較対象はウィンドウサイズとPKDrawingのサイズ
  • 4パターンになるはず
  • (イコールは一旦考えない)
  1. Window Height > PKDrawing Height && Window Width > PKDrawing Width
  2. Window Height > PKDrawing Height && Window Width < PKDrawing Width
  3. Window Height < PKDrawing Height && Window Width > PKDrawing Width
  4. Window Height < PKDrawing Height && Window Width < PKDrawing Width

1のときは何もしなくていい
2、 3 のときは、ノートデータがはみ出るところはPKDrawingを優先、おさまるところはWindowサイズを入れる
4はPKDrawingを代入

これでたぶんオッケー

    private var isDrawingWiderThanWindow: Bool {
        canvasView.frame.width < canvasView.drawing.bounds.maxX
    }

    private var isDrawingHigherThanWindow: Bool {
        canvasView.frame.height < canvasView.drawing.bounds.maxY
    }

    func initialContentSize() {
        guard !canvasView.drawing.bounds.isNull else { return }

        if isDrawingWiderThanWindow, isDrawingHigherThanWindow {
            canvasView.contentSize = .init(width: canvasView.drawing.bounds.maxX,
                                           height: canvasView.drawing.bounds.maxY)
        } else if isDrawingWiderThanWindow, !isDrawingHigherThanWindow {
            canvasView.contentSize = .init(width: canvasView.drawing.bounds.maxX,
                                           height: canvasView.frame.height)
        } else if !isDrawingWiderThanWindow, isDrawingHigherThanWindow {
            canvasView.contentSize = .init(width: canvasView.frame.width,
                                           height: canvasView.drawing.bounds.maxY)
        }

        canvasView.contentOffset = .zero
    }

しかし……一度大きいDrawingを編集してから、ウィンドウにおさまるサイズのノート描くと、前のサイズが残っちゃうな。
PKCanvasView の使いまわしが良くないな。
なんでこんな設計にしたんだっけな……

今回のアップデートはとりあえず開けなくなるのを回避できればいいわ
根本的にはPKCanvasView の使い回しをやめるとか、DelegateのところをCoordinatorにするとかあるけども

最近PoPで絵描いたときに、ピンチイン/ピンチアウトがどうしても欲しいなと思った

https://github.com/0si43/PiecesOfPaper/issues/49

MagnificationGesture だと、ちょっと違う挙動になる

UIScrollViewのDelegateメソッドで簡単にできることが判明

CanvasDelegateBridgeObject という、PencilKitのprotocolに準拠するためだけの存在を解体したい

こんなんで受けられそう

struct PKCanvasViewWrapper: UIViewRepresentable {
    @Binding var canvasView: PKCanvasView

    func makeUIView(context: Context) -> PKCanvasView {
        canvasView.tool = PKInkingTool(.pen, color: .black, width: 1)
        canvasView.delegate = context.coordinator
        return canvasView
    }

    func updateUIView(_ canvasView: PKCanvasView, context: Context) { }

    func makeCoordinator() -> Coordinator {
        Coordinator()
    }

    class Coordinator: NSObject, PKCanvasViewDelegate {
        func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
            print("temp")
        }

        private func updateContentSizeIfNeeded(_ canvasView: PKCanvasView) {
            print("temp")
        }
    }
}

シェアシートはなんでCanvasDelegateBridgeObject でプロトコル準拠させてるんだっけ?
UIActivityViewControllerWrapper で準拠する方が適切では?

https://qiita.com/ezura/items/6036c6e100599b601482
func makeUIViewController(context: UIViewControllerRepresentableContext<UIActivityViewControllerWrapper>)
                                -> UIActivityViewController {}

これ、なんでこんな悲惨なことになってるのかと思ったら、Xcodeのスタブ補完で入れたのかな

func makeUIViewController(context: Context) -> UIActivityViewController {}

これでOK

UIKitベースのライブラリのDelegateメソッド受けるのを、Coordinatorに移譲する作業DONE

CanvasRouter が変な処理になってるから、これも解体したいけど、SwiftUIの画面遷移のベストプラクティスがよくわかんないな

becomeFirstResponder って実はよくわかってないな

PKCanvasViewPKCanvasViewWrapper に持たせる設計に書き直したんだけど、パレットの制御が上手くできなくなった

    func updateUIView(_ canvasView: PKCanvasView, context: Context) {
        context.coordinator.toolPicker.setVisible(showToolPicker, forFirstResponder: canvasView)
        canvasView.becomeFirstResponder()
    }

これで挙動は期待したものになった

=== AttributeGraph: cycle detected through attribute 597016 ===
2022-04-16 10:27:21.631559+0900 Pieces of Paper[71685:3161582] [UIFocus] Failed to update focus with context <UIFocusUpdateContext: 0x280512c60: previouslyFocusedItem=(null), nextFocusedItem=(null), focusHeading=None>. No additional info available.

ただコンソールにこのメッセージが出まくるのが気になる

private var isDrawingWiderThanWindow: Bool {
     canvasView.frame.width < canvasView.drawing.bounds.maxX
}

ここの判定、canvasView.frameが0になっていて、Canvasの広さ判定をミスっていた

あまり使いたくなかったけど、矩形が取れないので、 UIScreen.main.bounds で判定するか

NoteDocument(あるいは UIDocument)でずっとテストデータにできなくて苦しんでいた。
fileURLにテキトーに https://www.google.com/ とかを入れると、落ちてしまっていた。
file:///xxx を入れると、イニシャルがとりあえず通ることに気づいた

使いやすいようにメソッドを生やした

static func createTestData() -> NoteDocument {
    guard let url = URL(string: "file:///test") else {
        fatalError()
    }
    
    return NoteDocument(fileURL: url, entity: NoteEntity(drawing: PKDrawing()))
}

Canvasを閉じたときのツールピッカーのディレイが気になるが、クリティカルじゃないので見送る
テストコードはもうちょっと充実させたいが、アプデ優先でいこう

このスクラップは1ヶ月前にクローズされました
ログインするとコメントできます