Issue #763
I usually break down a big struct into smaller views and extensions. For example I have a ClipboardCell
that has a lot of onReceive
so I want to move these to another component.
One way to do that is to extend ClipboardCell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 struct ClipboardCell : View { let isSelected: Bool @State var showsPreview: Bool @State var showsViewRaw: Bool let onCopy: () -> Void let onDelete: () -> Void } extension ClipboardCell { func onReceiveKeyboard () -> some View { self .onReceive( NotificationCenter .default .publisher(for : .didKeyboardCopyItem) .receive(on: RunLoop .main), perform: { note in onCopy() } ) } }
but then when we want to use this, we get some View has no member onReceiveKeyboard
as self
after some Swift modifier becomes some View
, unless we call onReceiveKeyboard
first
1 2 3 4 5 6 7 struct ClipboardCell : View { var body: some View { self .padding() .onReceiveKeyboard() } }
Use ViewModifier The SwiftUI is to use ViewModifier
where we can inject Binding and functions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 struct ClipboardCellOnKeyboardModifier : ViewModifier { let isSelected: Bool @Binding var showsPreview: Bool @Binding var showsViewRaw: Bool let onCopy: () -> Void let onDelete: () -> Void func body (content: Content) -> some View { content.onReceive( NotificationCenter .default .publisher(for : .didKeyboardCopyItem) .receive(on: RunLoop .main), perform: { _ in guard isSelected else { return } onCopy() } ) } } `
Then we can consume it and pass parameters
1 2 3 4 5 6 7 8 9 10 11 12 struct ClipboardCell : View { var body: some View { self .padding() .modifier( ClipboardCellOnKeyboardModifier ( showsPreview: Binding <Bool >(get : {}, set : {}) , showsViewRaw: Binding <Bool >(get : {}, set : {}) ) ) } }
Pass State and Binding For now SwiftUI seems to have a bug that ViewModifier does not listen to onReceive
, we can extend generic View
and pass parameters instead
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 extension View { func onClipboardCellReceiveKeyboard ( isSelected: Bool, showsPreview: Binding<Bool>, showsViewRaw: Binding<Bool>, onCopy: () -> Void , onDelete: () -> Void ) -> some View { self .onReceive( NotificationCenter .default .publisher(for : .didKeyboardCopyItem) .receive(on: RunLoop .main), perform: { _ in guard isSelected else { return } onCopy() } )
Use ObservableObject Another way is to use an ObservableObject
and encapsulate logic and state in there, and share this across views that want to consume this set of data, just like a ViewModel
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import SwiftUIfinal class ItemsHolder : ObservableObject { @Published var items: [ClipboardItem ] = [] @Published var selectedItems = Set <ClipboardItem >() @Published var agos: [UUID : String ] = [:] func updateAgos () { agos.removeAll() for item in items { agos[item.id] = Formattes .ago(date: item.createdAt) } } func update (items: [ClipboardItem]) { self .items = items .sorted(by: { $0 .createdAt > $1 .createdAt }) updateAgos() } } struct ClipboardCell : View { @StateObject var itemsHolder = ItemsHolder () var body: some View { list.onReceive( NotificationCenter .default .publisher(for : .didKeyboardCopyItem) .receive(on: RunLoop .main), perform: { note in itemsHolder.onCopy() } ) } }
Updated at 2021-01-29 12:51:52