SwiftUIで勘違いした話

 SwiftUIで勘違いした話

最近、本格的にSwiftUIを触り始めたところ、この前、盛大に勘違いしてやらかしてしまいました。その経緯と関連するトピックがおもしろかったので紹介します。

どこで勘違いしてしまったか考えながら見てみて下さい。

3c031541ed3d92869414857dfef853de?s=128

Yuta Koshizawa

July 10, 2020
Tweet

Transcript

  1. Swi$ UIͰצҧ͍ͨ͠࿩ Yuta Koshizawa @koher

  2. @koher • try! Swi* Tokyo • 2016, 2018 • iOSDC

    Japan • 2017, 2018, 2019 • Qiita • "Swi*ͷOp?onalܕΛۃΊΔ" • "JavaϓϩάϥϚͷͨΊͷKotlinೖ໳" • ...
  3. Swi$ UIͰצҧ͍ͨ͠࿩ ! ๻͕Ͳ͜Ͱצҧ͍͔ͨ͠ ߟ͑ͳ͕Βฉ͍ͯΈͯԼ͍͞

  4. None
  5. None
  6. None
  7. None
  8. None
  9. struct CounterView: View { @State var count: Int = 0

    var body: some View { VStack { Text("\(count)") .font(.largeTitle) Stepper("count", value: $count) .labelsHidden() } } }
  10. struct CounterView: View { @State var count: Int = 0

    var body: some View { VStack { Text("\(count)") .font(.largeTitle) Stepper("count", value: $count) .labelsHidden() } } }
  11. SE-0258: Property Wrappers1 Status: Implemented (Swi. 5.1) • @State •

    @ObservedObject • @Binding • @Published • ... 1 h$ps:/ /github.com/apple/swi6-evolu9on/blob/master/proposals/0258-property-wrappers.md
  12. projectedValue2 $count Property Wrapper projectedValue @State Binding<Value> @Binding Binding<Value> @Published

    Published<Value>.Publisher 2 h$ps:/ /docs.swi/.org/swi/-book/LanguageGuide/Proper<es.html#ID619
  13. struct CounterView: View { @State var count: Int = 0

    var body: some View { VStack { Text("\(count)") .font(.largeTitle) Stepper("count", value: $count) .labelsHidden() } } }
  14. struct CounterView: View { @State var count: Int = 0

    var body: some View { VStack { Text("\(count)") .font(.largeTitle) Stepper("count", value: $count) .labelsHidden() } } }
  15. struct CounterView: View { @State var count: Int = 0

    var body: some View { VStack { Text("\(count)") .font(.largeTitle) Stepper("count", value: $count) .labelsHidden() } } }
  16. import Combine final class Counter: ObservableObject { @Published var count:

    Int = 0 }
  17. struct CounterView: View { @ObservedObject var counter: Counter var body:

    some View { VStack { Text("\(counter.count)") .font(.largeTitle) Stepper("count", value: $counter.count) .labelsHidden() } } }
  18. struct CounterView: View { @ObservedObject var counter: Counter var body:

    some View { VStack { Text("\(counter.count)") .font(.largeTitle) Stepper("count", value: $counter.count) .labelsHidden() } } }
  19. struct CounterView: View { @ObservedObject var counter: Counter var body:

    some View { VStack { Text("\(counter.count)") .font(.largeTitle) Stepper("count", value: $counter.count) .labelsHidden() } } }
  20. None
  21. struct NumberDisplay: View { @Binding var number: Int var body:

    some View { return HStack { ForEach(digits(from: number)) { Image(systemName:"\($0.value).circle.fill") .resizable() .frame(width: 64, height: 64) } } } ... }
  22. struct NumberDisplay: View { @Binding var number: Int var body:

    some View { return HStack { ForEach(digits(from: number)) { Image(systemName:"\($0.value).circle.fill") .resizable() .frame(width: 64, height: 64) } } } ... }
  23. struct CounterView: View { @ObservedObject var counter: Counter var body:

    some View { VStack { NumberDisplay(number: $counter.count) Stepper("count", value: $counter.count) .labelsHidden() } } }
  24. struct CounterView: View { @ObservedObject var counter: Counter var body:

    some View { VStack { NumberDisplay(number: $counter.count) Stepper("count", value: $counter.count) .labelsHidden() } } }
  25. import Combine final class Counter: ObservableObject { @Published var count:

    Int = 0 }
  26. import Combine final class Counter: ObservableObject { @Published private(set) var

    count: Int = 0 func increment() { count += 1 } func reset() { count = 0 } }
  27. None
  28. None
  29. None
  30. None
  31. struct CounterView: View { @ObservedObject var counter: Counter var body:

    some View { VStack { NumberDisplay(number: $counter.count) // HStack { Button("Reset") { self.counter.reset() } Button("Increment") { self.counter.increment() } } } } }
  32. struct CounterView: View { @ObservedObject var counter: Counter var body:

    some View { VStack { NumberDisplay(number: $counter.count) // HStack { Button("Reset") { self.counter.reset() } Button("Increment") { self.counter.increment() } } } } }
  33. struct CounterView: View { @ObservedObject var counter: Counter var body:

    some View { VStack { NumberDisplay(number: $counter.count) // HStack { Button("Reset") { self.counter.reset() } Button("Increment") { self.counter.increment() } } } } }
  34. $counter.count ͳΜͰ Counter ͷϓϩύςΟʹ ΞΫηεͰ͖Δͷʁ

  35. ObservedObject.projectedValue3 var projectedValue: ObservedObject<ObjectType>.Wrapper 3 h$ps:/ /developer.apple.com/documenta6on/swi9ui/observedobject/projectedvalue

  36. ObservedObject.Wrapper .subscript(dynamicMember:)4 subscript<Subject>( dynamicMember keyPath: ReferenceWritableKeyPath<ObjectType, Subject> ) -> Binding<Subject>

    4 h$ps:/ /developer.apple.com/documenta6on/swi9ui/observedobject/wrapper/subscript(dynamicmember:)
  37. ObservedObject.Wrapper .subscript(dynamicMember:)4 subscript<Subject>( dynamicMember keyPath: ReferenceWritableKeyPath<ObjectType, Subject> ) -> Binding<Subject>

    4 h$ps:/ /developer.apple.com/documenta6on/swi9ui/observedobject/wrapper/subscript(dynamicmember:)
  38. SE-0195: Introduce User-defined "Dynamic Member Lookup" Types 5 Status: Implemented

    (Swi. 4.2) @dynamicMemberLookup enum JSON { ... subscript(dynamicMember member: String) -> JSON? { ... } } 5 h$ps:/ /github.com/apple/swi6-evolu9on/blob/master/proposals/0195-dynamic-member-lookup.md
  39. SE-0252: Key Path Member Lookup6 Status: Implemented (Swi. 5.1) @dynamicMemberLookup

    struct Lens<T> { ... subscript<U>(dynamicMember keyPath: WritableKeyPath<T, U>) -> Lens<U> { ... } } 6 h$ps:/ /github.com/apple/swi6-evolu9on/blob/master/proposals/0252-keypath-dynamic-member-lookup.md
  40. ObservedObject.Wrapper subscript<Subject>( dynamicMember keyPath: ReferenceWritableKeyPath<ObjectType, Subject> ) -> Binding<Subject>

  41. ObservedObject.Wrapper subscript<Subject>( dynamicMember keyPath: ReferenceWritableKeyPath<ObjectType, Subject> ) -> Binding<Subject>

  42. ૒ํ޲όΠϯσΟϯά ( Stepper ) ↓ Ұํ޲όΠϯσΟϯά ( NumberDisplay )

  43. ObservedObject.Wrapper subscript<Subject>( dynamicMember keyPath: ReferenceWritableKeyPath<ObjectType, Subject> ) -> Binding<Subject>

  44. ReadOnly subscript<Subject>( dynamicMember keyPath: KeyPath<ObjectType, Subject> ) -> Binding<Subject>

  45. struct CounterView: View { @ObservedObject var counter: Counter var body:

    some View { VStack { NumberDisplay(number: $counter.count) // HStack { Button("Reset") { self.counter.reset() } Button("Increment") { self.counter.increment() } } } } }
  46. struct CounterView: View { @ObservedObject var counter: Counter var body:

    some View { VStack { NumberDisplay(number: $counter.readOnly // .count) HStack { Button("Reset") { self.counter.reset() } Button("Increment") { self.counter.increment() } } } } }
  47. ObservedObject.Wrapper.ReadOnly extension ObservedObject.Wrapper { var readOnly: ReadOnly { return ReadOnly(unsafeBitCast(self,

    to: ObjectType.self)) // } @dynamicMemberLookup struct ReadOnly { private let object: ObjectType init(_ object: ObjectType) { self.object = object } subscript<Subject>(dynamicMember keyPath: KeyPath<ObjectType, Subject>) -> Binding<Subject> { Binding<Subject>( get: { self.object[keyPath: keyPath] }, set: { _ in assertionFailure("Read-only") } ) } } }
  48. One-way data binding from @ObservedObject with Swi9UI7 Is it possible

    to achieve one-way data binding with Swi6UI in situa9ons like below? import Combine final class Counter: ObservableObject { @Published private(set) var count: Int = 0 func increment() { count += 1 } func reset() { count = 0 } } ... 7 h$ps:/ /forums.swi1.org/t/one-way-data-binding-from-observedobject-with-swi1ui/37547
  49. struct NumberDisplay: View { @Binding var number: Int var body:

    some View { return HStack { ForEach(digits(from: number)) { Image(systemName:"\($0.value).circle.fill") .resizable() .frame(width: 64, height: 64) } } } ... }
  50. struct NumberDisplay: View { let number: Int var body: some

    View { return HStack { ForEach(digits(from: number)) { Image(systemName:"\($0.value).circle.fill") .resizable() .frame(width: 64, height: 64) } } } ... }
  51. struct CounterView: View { @ObservedObject var counter: Counter var body:

    some View { VStack { NumberDisplay(number: $counter.readOnly .count) HStack { Button("Reset") { self.counter.reset() } Button("Increment") { self.counter.increment() } } } } }
  52. struct CounterView: View { @ObservedObject var counter: Counter var body:

    some View { VStack { NumberDisplay(number: counter.count) HStack { Button("Reset") { self.counter.reset() } Button("Increment") { self.counter.increment() } } } } }
  53. όΠϯσΟϯά͠ͳ͍ͱ มߋ͕൓ө͞Εͳ͘ͳ͍ʁ

  54. Swi$UI ͷ View ͸ Ծ૝ View ͳͷͰ͜ΕͰ OK

  55. Swi$UI ͷجૅதͷجૅ

  56. ͳΜͰצҧ͍ͨ͠ʁ • Ծ૝ View ͷߟ͑ํΛཧղͯ͠Δͭ΋Γ͚ͩͬͨͲ׳Ε͕଍Γ ͳ͔ͬͨ • ૒ํ޲όΠϯσΟϯά → Ұํ޲όΠϯσΟϯά

    ͷॱʹߟ͑ͨ • Swi)UI ͸·ͩ·ͩػೳ͕଍Γͳ͍ͱ͍͏ઌೖ؍͕͋ͬͨ
  57. ஏ͔͍ͣ͠ࢥ͍Λ͚ͨ͠ΕͲ΋ 3 ෼Ͱ໰୊͕ղܾͨ͠

  58. Θ͔Βͳ͍͜ͱΛஏ͔͕ͣ͠Βͣ ࣭໰͢Δ͜ͱ͸େࣄ

  59. Ͱ΋ɺ΍ͬͺΓஏ͔͍ͣ͠

  60. Swi$ Zoomin' #3 ಗ໊࣭໰ձ ໌೔ 21:00- h"ps:/ /swi)-tweets.connpass.com/event/176665/

  61. None