$30 off During Our Annual Pro Sale. View Details »

Backport AsyncImage

Backport AsyncImage

集まれSwift好き!Swift愛好会 vol.61 @ オンライン https://love-swift.connpass.com/event/218651/

Yutaro Muta

July 21, 2021
Tweet

More Decks by Yutaro Muta

Other Decks in Programming

Transcript

  1. Backport AsyncImage
    2021/07/21 @ू·ΕSwift޷͖ʂSwiftѪ޷ձ vol.61


    Yutaro Muta @yutailang0119

    View Slide

  2. • Yutaro Muta @yutailang0119

    • Hatena Co., Ltd. @Kyoto

    • Conference Sta
    f
    Who am I ?

    View Slide

  3. Goal
    • SwiftUI.AsyncImage Λ஌Δ


    • ࠓ೔ (Xcode 12.5.1) ͔Β࢖͑Δ SBPAsyncImage


    • SBPAsyncImage ͔ΒֶͿ SwiftUI Trick

    View Slide

  4. Environment
    • Xcode Version 13.0 beta 3 (13A5192i)


    • Xcode Version 12.5.1 (12E507)

    View Slide

  5. SwiftUI.AsyncImage

    View Slide

  6. SwiftUI.AsyncImage
    • > A view that asynchronously loads and displays an image.


    • Available on Xcode 13
    struct AsyncImage where Content : View
    https://developer.apple.com/documentation/swiftui/asyncimage

    View Slide

  7. SwiftUI.AsyncImage
    AsyncImage(url: URL(string: “https://example.com/icon.png"))


    .frame(width: 200, height: 200)


    AsyncImage(url: URL(string: "https://example.com/icon.png")) { image in


    image.resizable()


    } placeholder: {


    ProgressView()


    }


    .frame(width: 50, height: 50)


    View Slide

  8. SwiftUI.AsyncImage
    Xcode 13 beta 3ݱࡏ


    • ⚠ URLRequestʹະରԠ


    • ϦΫΤετຖʹΩϟογϡϙϦγʔ΍λΠϜΞ΢τͳͲΛίϯτϩʔϧ
    Ͱ͖ͳ͍


    • ⚠ ௚઀తͳը૾ͷΩϟογϡػೳ͸ͳ͍

    View Slide

  9. • ⚠ Availability


    • iOS 15.0+ Beta


    • macOS 12.0+ Beta


    • Mac Catalyst 15.0+ Beta


    • tvOS 15.0+ Beta


    • watchOS 8.0+ Beta
    SwiftUI.AsyncImage

    View Slide

  10. ࠓ͙͢ SwiftUI.AsyncImage Λ࢖͍͍ͨ

    View Slide

  11. yutailang0119/SBPAsyncImage

    View Slide

  12. yutailang0119/SBPAsyncImage
    • > Backport of SwiftUI.AsyncImage to iOS 14, macOS 11, tvOS 14 and
    watchOS 7 and earlier.


    > SBPAsyncImage provides interface and behavior of AsyncImage to
    earlier OS.


    • Available on Xcode 12.5.1
    struct BackportAsyncImage where Content : View


    typealias AsyncImage = BackportAsyncImage
    https://github.com/yutailang0119/SBPAsyncImage

    View Slide

  13. yutailang0119/SBPAsyncImage
    BackportAsyncImage(url: URL(string: “https://example.com/icon.png"))


    .frame(width: 200, height: 200)


    SBPAsyncImage.AsyncImage(url: URL(string: "https://example.com/icon.png")) { image in


    image.resizable()


    } placeholder: {


    ProgressView()


    }


    .frame(width: 50, height: 50)


    View Slide

  14. Availability


    • iOS 13.0+


    • macOS 10.5+


    • Mac Catalyst 13.0+


    • tvOS 13.0+


    • watchOS 6.0+
    yutailang0119/SBPAsyncImage

    View Slide

  15. Availability


    • iOS 13.0+


    • macOS 10.5+


    • Mac Catalyst 13.0+


    • tvOS 13.0+


    • watchOS 6.0+
    yutailang0119/SBPAsyncImage
    ✅ Cover all SwiftUI platforms

    View Slide

  16. yutailang0119/SBPAsyncImage
    • ⚠ SwiftUI.AsyncImage Λ׬શʹτϨʔεͰ͖ͯ͸͍ͳ͍


    • ྫ͑͹ɺ௨৴ͷΩϟϯηϧ


    • SwiftUI.AsyncImage ͸ task(_:) Ͱ௨৴Λ։͍࢝ͯͦ͠͏


    • ⚠ macOS ͰScaleʹରԠͰ͖͍ͯͳ͍


    • https://github.com/yutailang0119/SBPAsyncImage/issues/8


    • Welcome Pull Request 🥴

    View Slide

  17. SBPAsyncImage Tricks

    View Slide

  18. Case1: @ObservableObject ͸


    ඳըຖʹinit͞ΕΔ

    View Slide

  19. Case1: @ObservableObject ͸ඳըຖʹinit͞ΕΔ
    • @ObservableObject Ͱఆٛ͢ΔϓϩύςΟ͸ɺRootͷView͕ඳը͞ΕΔ
    ຖʹinit͞ΕΔ


    • ObservedObject ͕࣋ͭ஋͸อ࣋͞Εͳ͍
    https://developer.apple.com/documentation/combine/observableobject

    View Slide

  20. • https://github.com/yutailang0119/SBPAsyncImage/pull/5 Ҏલ͸ɺҎԼͷ
    Α͏ͳ࢖͍ํΛ͢ΔͱɺϘλϯΛԡ͢౓ʹ௨৴͞Ε͍ͯͨ


    • ϘλϯΛԡ͢౓ʹɺҰ౓ը૾͕ۭʹͳΔ
    struct ContentView: View {


    @State var count: Int = 0


    var body: some View {


    VStack {


    SBPAsyncImage.AsyncImage(url: URL(string: "https://example.com/icon.png"))


    Button {


    count += 1


    } label: {


    Text("Count: \(count)")


    }


    }


    }


    }
    Case1: @ObservableObject ͸ඳըຖʹinit͞ΕΔ

    View Slide

  21. • @StateObject Λ࢖ͬͯղܾ


    • ⚠ Availability


    • iOS 14.0+


    • macOS 11.0+


    • Mac Catalyst 14.0+


    • tvOS 14.0+


    • watchOS 7.0+
    https://developer.apple.com/documentation/swiftui/stateobject
    Case1: @ObservableObject ͸ඳըຖʹinit͞ΕΔ

    View Slide

  22. • @StateObject Λ࢖͑ͳ͍ҎલͷOSͰ͸ @ObservableObject ͱ @State
    Λ૊Έ߹ΘͤͯɺৼΔ෣͍Λ໛฿͢Δ


    • ͘Θ͘͠͸ `iOS 13Ͱ΋StateObject͕࢖͍͍ͨʂ`


    • https://kouki.hatenadiary.com/entry/2021/06/22/130000
    Case1: @ObservableObject ͸ඳըຖʹinit͞ΕΔ

    View Slide

  23. Case2: StateObject Λ


    ௚઀initͯ͠͸͍͚ͳ͍

    View Slide

  24. • StateObject.init(wrappedValue:)


    • > You don’t call this initializer directly. Instead, declare a property with
    the @StateObject attribute in a View, App, or Scene, and provide an
    initial value:


    • initΛ௚઀࣮ߦ͢ΔͱɺSwiftUIͷϝϞϦ؅ཧ΍ @autoclosure ͷ࠷దԽ͕
    ޮ͔ͳ͘ͳΔ
    https://developer.apple.com/documentation/swiftui/stateobject/init(wrappedvalue:)
    Case2: StateObject Λ௚઀initͯ͠͸͍͚ͳ͍
    init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType)

    View Slide

  25. • https://github.com/yutailang0119/SBPAsyncImage/pull/14 Ҏલ͸ɺҎԼ
    ͷΑ͏ʹStateObjectΛ௚઀init͍ͯͨ͠
    @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)


    private struct ContentBody: View {


    @StateObject private var viewModel: ViewModel


    private let content: (AsyncImagePhase) -> Content


    init(viewModel: ViewModel,


    @ViewBuilder content: @escaping (AsyncImagePhase) -> Content) {


    self._viewModel = .init(wrappedValue: viewModel)


    self.content = content


    }


    }
    Case2: StateObject Λ௚઀initͯ͠͸͍͚ͳ͍

    View Slide

  26. • @StateObject ͷinit͸Viewʹ೚ͤͯɺStateObject͕ߦ͍͍ͨॲཧ͸ɺ
    onAppear(perform:) ʹҠಈ


    • ͘Θ͘͠͸ `Explanation behind the error? “Accessing StateObject’s
    object without being installed on a View. This will create a new
    instance each time.”`


    • https://forums.swift.org/t/explanation-behind-the-error-accessing-
    stateobjects-object-without-being-installed-on-a-view-this-will-
    create-a-new-instance-each-time/40111
    Case2: StateObject Λ௚઀initͯ͠͸͍͚ͳ͍

    View Slide

  27. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)


    private struct ContentBody: View {


    @StateObject private var viewModel = ViewModel()


    ...


    var body: some View {


    content(viewModel.phase)


    .onAppear {


    viewModel.download(url: url, scale: scale, transaction: transaction)


    }


    }


    }
    Case2: StateObject Λ௚઀initͯ͠͸͍͚ͳ͍

    View Slide

  28. ·ͱΊ

    View Slide

  29. ·ͱΊ
    • SwiftUI.AsyncImage ศརͰ͢Ͷ


    • ૈ͍Օॴ΋͋Δ


    • yutailang0119/SBPAsyncImage ΛΑΖ͘͠


    • SwiftUI׆༻ʹ͸Trick͕ຬࡌ

    View Slide

  30. Reference
    • https://developer.apple.com/documentation/swiftui/asyncimage


    • https://github.com/yutailang0119/SBPAsyncImage


    • https://developer.apple.com/documentation/combine/observableobject


    • https://developer.apple.com/documentation/swiftui/stateobject


    • https://kouki.hatenadiary.com/entry/2021/06/22/130000


    • https://developer.apple.com/documentation/swiftui/stateobject/init(wrappedvalue:)


    • https://forums.swift.org/t/explanation-behind-the-error-accessing-stateobjects-object-without-
    being-installed-on-a-view-this-will-create-a-new-instance-each-time/40111

    View Slide

  31. &OKPZ"TZOD*NBHF 5IBOLT
    w NVUBZVUBSP!HNBJMDPN
    w IUUQTUXJUUFSDPNZVUBJMBOH
    w IUUQTHJUIVCDPNZVUBJMBOH

    View Slide