user1046037
user1046037

Reputation: 17685

FocusedValue / FocusedBinding not working on iPad

Aim

Problem

Question:

Environment

Code

@main
struct TestApp: App {
    @State private var price = 0

    var body: some Scene {
        WindowGroup {
            ContentView(price: $price)
                .focusedValue(\.price, $price)
        }
        .commands {
            PriceCommands()
        }
    }
}

struct ContentView: View {
    
    @Binding var price: Int
    
    var body: some View {
        IncreasePriceButton(price: $price)
    }
}

struct IncreasePriceButton: View {
    
    @Binding var price: Int
    
    var body: some View {
        Button("Increase Price") {
            price += 1
            print("price = \(price)")
        }
    }
}

struct PriceCommandButton: View {
    
    @FocusedBinding(\.price) var price
    
    var body: some View {
        Button("Print Price") {
            print("price = \(price)")
        }
    }
}

struct PriceCommands: Commands {
    var body: some Commands {
        CommandMenu("Custom") {
            PriceCommandButton()
                .keyboardShortcut(KeyboardShortcut("C", modifiers: [.command, .shift]))
        }
    }
}

struct FocusedPriceKey: FocusedValueKey {
    typealias Value = Binding<Int>
}

extension FocusedValues {
    var price: FocusedPriceKey.Value? {
        get { self[FocusedPriceKey.self] }
        set { self[FocusedPriceKey.self] = newValue }
    }
}

Upvotes: 1

Views: 454

Answers (1)

shufflingb
shufflingb

Reputation: 1947

So I took the demo code and added a little bit of debug as shown below.

As far as I can tell, there are two problems.

First that focusedValue on ContentView was not being set because the View is not being focused. Which can be seen because the @FocusBinding in OtherView doesn't pick up the price value as expected.

This could be a SwiftUI bug, but it's easy to work around by switching over to the focusedSceneValue modifier. Anyway, with that in place the price in ContentView and OtherView keeps in step.

The second problem is that even with the first fix/workaround in place, changes in the price are never causing the Custom menu to be rebuilt after it's initial rendering.

I think this is a bug with SwiftUI and would recommend submitting a feedback submission to Apple letting them know about it.

The workaround (in this case) with price being app global state, would be to just pass it through to PriceCommands as an argument (as is already being done for ContentView).

Kind regards

import SwiftUI

@main
struct TestApp: App {
    @State private var price = 0

    var body: some Scene {
        WindowGroup {
            ContentView(price: $price)
                .focusedSceneValue(\.price, $price) // <== Changed
        }
        .commands {
            PriceCommands()
        }
    }
}

struct OtherView: View {
    @FocusedBinding(\.price) var price: Int?

    var body: some View {
        Text("OtherView price = \(String(describing: price))")
    }
}

struct ContentView: View {
    @Binding var price: Int

    var body: some View {
        VStack {
            OtherView()
            IncreasePriceButton(price: $price)
        }
    }
}

struct IncreasePriceButton: View {
    @Binding var price: Int

    var body: some View {
        Button("Increase Price") {
            price += 1
            print("price = \(price)")
        }
    }
}

struct PriceCommandButton: View {
    @FocusedBinding(\.price) var price
    var priceText: String {
        String(price ?? -1)
    }

    var body: some View {
        print("Buiding commands menu with \(String(describing: price))")
        return Button("Print Price \(priceText)") {
            print("price = \(String(describing: price))")
        }
    }
}

struct PriceCommands: Commands {
    var body: some Commands {
        CommandMenu("Custom") {
            PriceCommandButton()
                .keyboardShortcut(KeyboardShortcut("C", modifiers: [.command, .shift]))
        }
    }
}

struct FocusedPriceKey: FocusedValueKey {
    typealias Value = Binding<Int>
}

extension FocusedValues {
    var price: FocusedPriceKey.Value? {
        get { self[FocusedPriceKey.self] }
        set { self[FocusedPriceKey.self] = newValue }
    }
}

Upvotes: 1

Related Questions