Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

• Yutaro Muta @yutailang0119 • Hatena Co., Ltd. @Kyoto • Conference Sta f Who am I ?

Slide 3

Slide 3 text

Goal • SwiftUI.AsyncImage Λ஌Δ • ࠓ೔ (Xcode 12.5.1) ͔Β࢖͑Δ SBPAsyncImage • SBPAsyncImage ͔ΒֶͿ SwiftUI Trick

Slide 4

Slide 4 text

Environment • Xcode Version 13.0 beta 3 (13A5192i) • Xcode Version 12.5.1 (12E507)

Slide 5

Slide 5 text

SwiftUI.AsyncImage

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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)

Slide 8

Slide 8 text

SwiftUI.AsyncImage Xcode 13 beta 3ݱࡏ • ⚠ URLRequestʹະରԠ • ϦΫΤετຖʹΩϟογϡϙϦγʔ΍λΠϜΞ΢τͳͲΛίϯτϩʔϧ Ͱ͖ͳ͍ • ⚠ ௚઀తͳը૾ͷΩϟογϡػೳ͸ͳ͍

Slide 9

Slide 9 text

• ⚠ Availability • iOS 15.0+ Beta • macOS 12.0+ Beta • Mac Catalyst 15.0+ Beta • tvOS 15.0+ Beta • watchOS 8.0+ Beta SwiftUI.AsyncImage

Slide 10

Slide 10 text

ࠓ͙͢ SwiftUI.AsyncImage Λ࢖͍͍ͨ

Slide 11

Slide 11 text

yutailang0119/SBPAsyncImage

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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)

Slide 14

Slide 14 text

Availability • iOS 13.0+ • macOS 10.5+ • Mac Catalyst 13.0+ • tvOS 13.0+ • watchOS 6.0+ yutailang0119/SBPAsyncImage

Slide 15

Slide 15 text

Availability • iOS 13.0+ • macOS 10.5+ • Mac Catalyst 13.0+ • tvOS 13.0+ • watchOS 6.0+ yutailang0119/SBPAsyncImage ✅ Cover all SwiftUI platforms

Slide 16

Slide 16 text

yutailang0119/SBPAsyncImage • ⚠ SwiftUI.AsyncImage Λ׬શʹτϨʔεͰ͖ͯ͸͍ͳ͍ • ྫ͑͹ɺ௨৴ͷΩϟϯηϧ • SwiftUI.AsyncImage ͸ task(_:) Ͱ௨৴Λ։͍࢝ͯͦ͠͏ • ⚠ macOS ͰScaleʹରԠͰ͖͍ͯͳ͍ • https://github.com/yutailang0119/SBPAsyncImage/issues/8 • Welcome Pull Request 🥴

Slide 17

Slide 17 text

SBPAsyncImage Tricks

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Case1: @ObservableObject ͸ඳըຖʹinit͞ΕΔ • @ObservableObject Ͱఆٛ͢ΔϓϩύςΟ͸ɺRootͷView͕ඳը͞ΕΔ ຖʹinit͞ΕΔ • ObservedObject ͕࣋ͭ஋͸อ࣋͞Εͳ͍ https://developer.apple.com/documentation/combine/observableobject

Slide 20

Slide 20 text

• 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͞ΕΔ

Slide 21

Slide 21 text

• @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͞ΕΔ

Slide 22

Slide 22 text

• @StateObject Λ࢖͑ͳ͍ҎલͷOSͰ͸ @ObservableObject ͱ @State Λ૊Έ߹ΘͤͯɺৼΔ෣͍Λ໛฿͢Δ • ͘Θ͘͠͸ `iOS 13Ͱ΋StateObject͕࢖͍͍ͨʂ` • https://kouki.hatenadiary.com/entry/2021/06/22/130000 Case1: @ObservableObject ͸ඳըຖʹinit͞ΕΔ

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

• 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)

Slide 25

Slide 25 text

• 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ͯ͠͸͍͚ͳ͍

Slide 26

Slide 26 text

• @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ͯ͠͸͍͚ͳ͍

Slide 27

Slide 27 text

@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ͯ͠͸͍͚ͳ͍

Slide 28

Slide 28 text

·ͱΊ

Slide 29

Slide 29 text

·ͱΊ • SwiftUI.AsyncImage ศརͰ͢Ͷ • ૈ͍Օॴ΋͋Δ • yutailang0119/SBPAsyncImage ΛΑΖ͘͠ • SwiftUI׆༻ʹ͸Trick͕ຬࡌ

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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