Search code examples
swiftswiftuiviewmodifier

ViewModifier for DynamicViewContent?


I would like to do something like this:

struct HandleUrlDropModifier<Content>: ViewModifier where Content: DynamicViewContent {
    @Environment(\.modelContext) var modelContext

    var content: Content

    func body(content: Content) -> some View {
        content
            .dropDestination(for: URL.self) { items, index in
                // index is Int
                handleUrlDrop(items, at: index)
            }
    }

    func handleUrlDrop(_ items: [URL], at index: Int) {
        modelContext.insert(...)
    }
}

Is that possible somehow?


What I've tried

Writing it as a direct extension doesn't work, since I want to use an @Environtment variable:

extension DynamicViewContent {
    @Environment(\.modelContext) var modelContext // <- not possible
    
    func handleUrlDrop() -> some View {
        self
        .dropDestination(for: URL.self) { items, index in
            modelContext.insert(...)
        }
   }
}

This works for regular Views, but the .dropDestination action is different for a DynamicViewContent:

struct HandleUrlDropModifier: ViewModifier {
    @Environment(\.modelContext) var modelContext

    func body(content: Content) -> some View {
        content
            .dropDestination(for: URL.self) { items, location in
                // location is CGPoint
                handleUrlDrop(items)
                return true
            }
    }

    func handleUrlDrop(_ items: [URL]) {
        modelContext.insert(...)
    }
}

Solution

  • Instead of a view modifier, write a View that wraps a DynamicViewContent and adds the dropDestination modifier. Then you can wrap this view with a DynamicViewContent extension, so that it looks like a view modifier.

    // this view can conform to DynamicViewContent too!
    struct HandleURLDropView<Content: DynamicViewContent>: DynamicViewContent {
        
        @Environment(\.modelContext) var modelContext
    
        let content: Content
        
        init(@ViewBuilder content: () -> Content) {
            self.content = content()
        }
        
        var body: some View {
            content.dropDestination(for: URL.self) { items, index in
                handleUrlDrop(items)
            }
        }
    
        func handleUrlDrop(_ items: [URL]) {
            // ...
        }
        
        // DynamicViewContent conformance
        var data: Content.Data { content.data }
    }
    
    extension DynamicViewContent {
        func handleURLDrop() -> some DynamicViewContent {
            HandleURLDropView {
                self
            }
        }
    }