Navigationbar-/Toolbar Button Not Working Reliable When @State Variable Refresh the View with a High Frequency

Navigationbar- / Toolbar Button not working reliable when @State variable refresh the view with a high frequency

The number state in provided variant refreshes entire view, that is a result of issue, and not very optimal for UI.

The solution for navigation bar, and in general good style, is to separate refreshing part into standalone view, so SwiftUI rendering engine rebuild only it.

Tested with Xcode 12b3 / iOS 14

struct TestOftenUpdate: View {

@State private var showModal = false

var body: some View {

NavigationView {
VStack {
QuickTimerView()
Button(action: { showModal.toggle() }, label: {
Text("Open Modal")
})
}
.navigationBarTitle("Home", displayMode: .inline)
.navigationBarItems(leading:
Button(action: {
showModal.toggle()
}, label: {
Text("Open Modal")
}))

.toolbar {
ToolbarItem(placement: .bottomBar) {
Button(action: {
showModal.toggle()
}, label: {
Text("Open Modal")
})
}
}
}

.sheet(isPresented: $showModal, content: {
Text("Hello, World!")
})

}
}

struct QuickTimerView: View {
@State private var number = 0

var body: some View {
VStack {
Text("Count \(number)")
Button(action: startTimer, label: {
Text("Start Timer")
})
}
}

func startTimer() {
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { (_) in
number += 1
}
}

}

With toolbar is different issue - it does not work after first sheet open even without timer view, and here is why

demo

as it is seen the button layout has corrupted and outside of toolbar area, that is why hit testing fails - and this is Apple defect that should be reported.

Workaround 1:

Place toolbar outside navigation view (visually toolbar is smaller, but sometimes might be appropriate, because button works)

    NavigationView {
// ... other code
}
.toolbar {
ToolbarItem(placement: .bottomBar) {
Button(action: {
showModal.toggle()
}, label: {
Text("Open Modal")
})
}
}

Timer publisher init timer after button click

Just put the timer in a child view and control its visibility with a bool. When the TimerView is removed the state is destroyed and the timer stops.

struct ContentView: View {
@State var started = false

var body: some View {
VStack {
Button(started ? "Stop" : "Start") {
started.toggle()
}
if started {
TimerView()
}
}
}
}

struct TimerView: View {

@State private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

...

SwiftUI: button in ToolbarItem(placement: .principal) not work after change it's label

It is known toolbar-sheet layout issue, see also here. You can file another feedback to Apple.

Here is a workaround for your case - using callback to update toolbar item after sheet closed. Tested with Xcode 12b5.

struct ContentView: View {
@State var show = false
@State var buttonTitle = "button A"

var body: some View {
NavigationView {
Text("Hello World!")
.toolbar {
ToolbarItem(placement: .principal) {
Button {
show.toggle()
} label: {
Text(buttonTitle)
}
.sheet(isPresented: $show) {
SelectTitle(buttonTitle: buttonTitle) {
self.buttonTitle = $0
}
}
}
}
}
}
}

struct SelectTitle: View {
@Environment(\.presentationMode) var presentationMode

@State private var buttonTitle: String
let callback: (String) -> ()

init(buttonTitle: String, callback: @escaping (String) -> ()) {
_buttonTitle = State(initialValue: buttonTitle)
self.callback = callback
}

var body: some View {
Button("Button B") {
buttonTitle = "Button B"
presentationMode.wrappedValue.dismiss()
}
.onDisappear {
callback(buttonTitle)
}
}
}

SwiftUI updating UI with high frequency data

You need to separate updating storage of frequency data from represented UI part, so storage receives/contains actual real data, but UI part update as soon as you only want (0.5 sec, 1 sec, 5 secs, etc.)

Here is possible approach. Tested with Xcode 12 / iOS 14.

import Combine
class Loop : ObservableObject {
private var storage: Int = 0
private var counter = PassthroughSubject<Int, Never>()

@Published var i : Int = 0 // only for UI

func startLoop() {
while true {
storage += 1 // update storage
counter.send(storage) // publish event
}
}

private var subscriber: AnyCancellable?
init() {
subscriber = counter
.throttle(for: 0.5, scheduler: DispatchQueue.global(qos: .background), latest: true) // drop in background
.receive(on: DispatchQueue.main) // only latest result
.sink { [weak self] (value) in // on @pawello2222 comment
self?.i = value
}

DispatchQueue.global(qos: .background).async {
self.startLoop()
}
}
}

Dynamic .sheet() with enum not showing the proper view after setting @State variable

You should use .sheet(item:) instead of .sheet(isPresented:), and to do so the enum needs to conform to Identifiable.
This should work:

@State private var activeSheet: Sheets?

enum Sheets: String, CaseIterable, Identifiable {
case Settings = "Settings",
Add = "Add"
var id: String {
return self.rawValue
}
}

var body: some View {
Menu(content: {
ForEach(Sheets.allCases, id: \.self) { sheet in
Button(sheet.rawValue, action: {
activeSheet = sheet
})
}
},
label: {
Image(systemName: "plus")
})
.sheet(item: $activeSheet) { sheet in
switch sheet {
case .Settings:
SettingsView(dismiss: $showingSheet, settings: $settings)
case .Add:
AddView()
}
}
}

Where are the Organizer and Breakpoints buttons in the workspace toolbar of XCode 5?

the Organizer button is removed from XCode 5. And for Breakpoints button, have a look at this image: Have a look



Related Topics



Leave a reply



Submit