I'm coming back to iOS development after years away and diving head-first into SwiftUI. It's a lot of fun, but I've hit a brick wall.
The scenario is I have a main view (which itself is a tabview, not important other than that it's not the top-level of the view hierarchy). This has subviews that rely on data coming back from a REST call to the cloud, but then some subviews need to turn around and make subsequent network calls to set up websockets for realtime updates.
In the main view's .onAppear, I fire off an async REST call, it returns JSON that gets parsed into a ModelView.
The ViewModel is declared in the top view like this:
class ViewModel: ObservableObject {
@Published var appData = CurrentREST() // Codables from JSON
@State var dataIsLoaded : Bool = false
func fetchData() async {
await _ = WebService().downloadData(fromURL: "current") { currentData in
DispatchQueue.main.async {
self.appData = currentData
self.dataIsLoaded = true
}
}
}
}
The main view declares the model view:
struct HomeTabView: View {
@ObservedObject var viewModel = ViewModel()
@Binding private var dataReceived: Bool
...
}
In the toplevel view, the REST call is triggered like this:
.onAppear {
if !viewModel.dataIsLoaded {
Task {
await viewModel.fetchData()
DispatchQueue.main.async {
self.dataReceived = true
}
}
}
}
The viewModel gets passed down to subviews so they can update themselves with the returned data. That part all works fine.
But it's the next step that break down. A subview needs to go back to the server and set up subscriptions to websockets, so it can do realtime updates from then on. It's this second step that is failing.
The dataReceived binding is set to true when the REST call has completed. The viewModel and dataReceived flags are passed down to the subviews:
SummaryView(viewModel: viewModel, dataIsLoaded: self.dataReceived)
What needs to happen next is inside the subview to call a function to wire up the next websocket steps. I've tried setting up:
struct SummaryView: View { @ObservedObject var viewModel: ViewModel @State var dataIsLoaded: Bool = false ... }.onChange(of: dataIsLoaded) { setupWebSocket() }
Problem is, the onChange never gets called.
I've tried various permutations of setting up a @State and a @Binding on the view model, and a separate @State on the main view. None of them get called and the subview's function that wires up the websockets never gets called.
The basic question is:
How do you trigger a cascading series of events through SwiftUI so external events (a network call) can cascade down to subviews and from there, their own series of events to do certain things.
I haven't gone deep into Combine yet, so if that's the solution, I'll go there. But I thought I'd ask and see if there was a simpler solution.
Any suggestions or pointers to best practices/code are most appreciated.
Post
Replies
Boosts
Views
Activity
I have a SwiftUI page that I want to simplify by showing basic information by default, and putting the additional info behind a "Details" DisclosureGroup for advanced users.
I started by laying out all the components and breaking things into individual Views. These all are laid out and look fine.
Then I took several of them and added them inside a DisclosureGroupView.
But all of a sudden, the views inside started getting crunched together and the contents of the DisclosureGroup got clipped about 2/3 of the way down the page. The problem I'm trying to solve is how to show everything inside the DIsclosureGroup.
The top-level View looks like this:
VStack {
FirstItemView()
SecondView()
DetailView() // <- Shows disclosure arrow
}
Where DetailView is:
struct DetailView: View {
@State var isExpanded = true
var body: some View {
GeometryReader { geometry in
DisclosureGroup("Details", isExpanded: $isExpanded) {
ThirdRowView()
Spacer()
FourthRowView()
VStack {
FifthRowWithChartView()
CaptionLabelView(label: "Third", iconName: "chart.bar.xaxis")
}
}
}
}
}
The FifthRowWithChartView is half-clipped. One thing that might contribute is that there is a Chart view at the bottom of this page.
I've tried setting the width and height of the DisclosureGroup based on the height returned by the GeometryReader, but that didn't do anything.
This is all on iOS 17.6, testing on an iPhone 15ProMax. Any tips or tricks are most appreciated.
Have a SwiftUI TextField with .numberPad keypad. Want to allow the user to make their changes, then hit 'Done' or 'Cancel' to dismiss the keyboard (there are other choices on the page) before hitting 'Submit'.
The view containing the field is brought up as a modal, using .FullScreenCover.
Here's the code snippet:
@State var currentBid: Float = 0
@FocusState var isInputActive: Bool
...
TextField("Enter Amount", text: Binding(
get: { String(Int(currentBid)) },
set: { currentBid = Float(Int($0) ?? 0) })
).keyboardType(.numberPad)
.focused($isInputActive)
.toolbar(content: {
ToolbarItemGroup(placement: .keyboard, content: {
Button("Cancel") {
print("CANCELLED")
isInputActive = false
}
Spacer()
Button("Done") {
print("DONE")
isInputActive = false
}
})
})
When you tap the text field, the numeric keyboard comes up, but the button bar does not show. This is on iOS 17.6. tested on-device.
The alternative is to use a regular alphanumeric keyboard with an "ENTER" button. But the field is to enter a number and I have to either filter the value or discard it in case they enter bad data.
I checked online, and others indicated wrapping the whole View inside a NavigationStack might be needed. Tried it and it didn't work.
Any suggestions? Thanks!