The exact error is: Unable to create bundle at URL (file:///System/Library/CoreServices/SystemVersion.bundle): does not exist or not a directory (0).
SwiftUI
RSS for tagProvide views, controls, and layout structures for declaring your app's user interface using SwiftUI.
Post
Replies
Boosts
Views
Activity
Hello,
I have been working on SwiftData since a month now and i found very weird that every time i update a data inside a SwiftData model my app became very slow.
I used the instrument if something was wrong, and i see memory increasing without releasing.
My app have a list with check and unchecked elements (screeshot below).
I just press check and unchecked 15 times and my memory start from 149mb to 361mb (screenshots below).
For the code i have this model.
final class Milestone: Identifiable {
// *********************************************************************
// MARK: - Properties
@Transient var id = UUID()
@Attribute(.unique) var uuid: UUID
var text: String
var goalId: UUID
var isFinish: Bool
var createdAt: Date
var updatedAt: Date
var goal: Goal?
init(from todoTaskResponse: TodoTaskResponse) {
self.uuid = todoTaskResponse.id
self.text = todoTaskResponse.text
self.goalId = todoTaskResponse.goalId
self.isFinish = todoTaskResponse.isFinish
self.createdAt = todoTaskResponse.createdAt
self.updatedAt = todoTaskResponse.updatedAt
}
init(uuid: UUID, text: String, goalId: UUID, isFinish: Bool, createdAt: Date, updatedAt: Date, goal: Goal? = nil) {
self.uuid = uuid
self.text = text
self.goalId = goalId
self.isFinish = isFinish
self.createdAt = createdAt
self.updatedAt = updatedAt
self.goal = goal
}
}
For the list i have
var milestonesView: some View {
ForEach(milestones) { milestone in
MilestoneRowView(task: milestone) {
milestone.isFinish.toggle()
}
.listRowBackground(Color.backgroundComponent)
}
.onDelete(perform: deleteMilestone)
}
And this is the cell
struct MilestoneRowView: View {
// *********************************************************************
// MARK: - Properties
var task: Milestone
var action: () -> Void
init(task: Milestone, action: @escaping () -> Void) {
self.task = task
self.action = action
}
var body: some View {
Button {
action()
} label: {
HStack(alignment: .center, spacing: 8) {
Image(systemName: task.isFinish ? "checkmark.circle.fill" : "circle")
.font(.title2)
.padding(3)
.contentShape(.rect)
.foregroundStyle(task.isFinish ? .gray : .primary)
.contentTransition(.symbolEffect(.replace))
Text(task.text)
.strikethrough(task.isFinish)
.foregroundStyle(task.isFinish ? .gray : .primary)
}
.foregroundStyle(Color.backgroundComponent)
}
.animation(.snappy, value: task.isFinish)
}
}
Does someone have a idea?
Thanks
When I run Simulator on my iPhone 15 and iPhone device, the dynamic list of NavigationLink rows is created as expected. However, when I run the same code on my iPhone device, the dynamic list of NavigationLink rows is not made.
My code snippet:
List {
ForEach(searchResults, id: .self) { name in
NavigationLink {
Text(name)
Button(action: {
print("Button Clicked!")
dynamicObjectsHandler(thisObjectName: name)
}) {
Text("Toggle Object \(searchText)")
.navigationTitle("Dynamic Guiding Objects")
}
} label: {
Text(name)
}
}
}
Additional information:
iOS version: 17.6
iPhone 14 Pro
iOS Deployment Target 17.5
Xcode version 15.4
Overview
I've found inconsistency on view lifecycle events between UIKit and SwiftUI as the following shows when using UIVPageViewController and UIHostingController as one of its pages.
SwiftUI View
onAppear is only called at the first time to display and never called in the other cases.
UIViewController
viewDidAppear is not called at the first time to display, but it's called when the page view controller changes its page displayed.
The whole view structure is as follows:
UIViewController (root)
UIPageViewController (as its container view)
UIHostingController (as its page)
SwiftUI View (as its content view)
UIViewControllerRepresentable (as a part of its body)
UIViewController (as its content)
Environment
Xcode Version 15.4 (15F31d)
iPhone 15 Pro (iOS 17.5) (Simulator)
iPhone 8 (iOS 15.0) (Simulator)
Sample code
import UIKit
import SwiftUI
class ViewController: UIViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
private var pageViewController: UIPageViewController!
private var viewControllers: [UIViewController] = []
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
private func setup() {
pageViewController.delegate = self
pageViewController.dataSource = self
let page1 = UIHostingController(rootView: MainPageView())
let page2 = UIViewController()
page2.view.backgroundColor = .systemBlue
let page3 = UIViewController()
page3.view.backgroundColor = .systemGreen
viewControllers = [page1, page2, page3]
pageViewController.setViewControllers([page1], direction: .forward, animated: false)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
guard let pageViewController = segue.destination as? UIPageViewController else { return }
self.pageViewController = pageViewController
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
print("debug: \(#function)")
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
print("debug: \(#function)")
guard let viewControllerIndex = viewControllers.firstIndex(of: viewController) else { return nil }
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0, viewControllers.count > previousIndex else { return nil }
return viewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
print("debug: \(#function)")
guard let viewControllerIndex = viewControllers.firstIndex(of: viewController) else { return nil }
let nextIndex = viewControllerIndex + 1
guard viewControllers.count != nextIndex, viewControllers.count > nextIndex else { return nil }
return viewControllers[nextIndex]
}
}
struct MainPageView: View {
var body: some View {
VStack(spacing: 0) {
PageContentView()
PageFooterView()
}
.onAppear { print("debug: \(type(of: Self.self)) onAppear") }
.onDisappear { print("debug: \(type(of: Self.self)) onDisappear") }
}
}
struct PageFooterView: View {
var body: some View {
Text("PageFooterView")
.padding()
.frame(maxWidth: .infinity)
.background(Color.blue)
.onAppear { print("debug: \(type(of: Self.self)) onAppear") }
.onDisappear { print("debug: \(type(of: Self.self)) onDisappear") }
}
}
struct PageContentView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> some UIViewController {
PageContentViewController()
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
}
class PageContentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
private func setup() {
view.backgroundColor = .systemYellow
let label = UILabel()
label.text = "PageContentViewController"
label.font = .preferredFont(forTextStyle: .title1)
view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("debug: \(type(of: Self.self)) \(#function)")
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
print("debug: \(type(of: Self.self)) \(#function)")
}
}
Logs
// Display the views
debug: MainPageView.Type onAppear
debug: PageFooterView.Type onAppear
// Swipe to the next page
debug: pageViewController(_:viewControllerAfter:)
debug: pageViewController(_:viewControllerBefore:)
debug: PageContentViewController.Type viewDidDisappear(_:)
debug: pageViewController(_:didFinishAnimating:previousViewControllers:transitionCompleted:)
debug: pageViewController(_:viewControllerAfter:)
// Swipe to the previous page
debug: PageContentViewController.Type viewDidAppear(_:)
debug: pageViewController(_:didFinishAnimating:previousViewControllers:transitionCompleted:)
debug: pageViewController(_:viewControllerBefore:)
As you can see here, onAppear is only called at the first time to display but never called in the other cases while viewDidAppear is the other way around.
I'm building a macOS app using SwiftUI and I recently updated to xcode 14.3. Since then I've been debugging why none of my animations were working, it turned out that the NavigationSplitView or NavigationStack are somehow interfering with all the animations from withAnimation to .transition() and everything in between.
Is anyone else experiencing this, knows a work around or knows why this is happening?
I can set color of the SF symbol image in the application window but cannot do the same in the menu bar. I wonder how I can change the color in the menu?
import SwiftUI
@main
struct ipmenuApp: App {
var body: some Scene {
MenuBarExtra {
Image(systemName: "bookmark.circle.fill")
.renderingMode(.original)
.foregroundStyle(.red)
} label: {
Image(systemName: "bookmark.circle.fill")
.renderingMode(.original)
.foregroundStyle(.red)
}
}
}
xcodebuild -version
Xcode 15.0
Build version 15A240d
I'm creating an app that has a number of draggable views, and each of these views themselves contain child dropDestination views.
In iOS 17.x they are draggable as expected. However, in iOS 18 betas 1 & 2 a long press on these views does NOT pick them up (start the drag operation). I have not tested with macOS 15 betas but the issue may well exist there also.
Is this a bug in iOS 18 betas 1 & 2 or does the implementation need to be updated somehow for iOS 18?
Please see example code below:
Views:
import SwiftUI
extension View {
func dropTarget<T>(for payloadType: T.Type, withTitle title: String) -> some View where T: Transferable {
modifier(DropTargetViewModifer<T>(title: title))
}
}
struct DropTargetViewModifer<T>: ViewModifier where T: Transferable {
let title: String
func body(content: Content) -> some View {
content
.dropDestination(for: T.self) { items, location in
print("Item(s) dropped in \(title)")
return true
} isTargeted: { targted in
if targted {
print("\(title) targeted")
}
}
}
}
struct InnerTestView: View {
let title: String
let borderColor: Color
var body: some View {
ZStack {
Text(title)
.padding()
Rectangle()
.stroke(borderColor)
}
.contentShape(Rectangle())
}
}
struct TestView: View {
var body: some View {
VStack(spacing: 0.0) {
HStack(alignment: .top, spacing: 0.0) {
InnerTestView(title: "Drop Zone 1", borderColor: .pink)
.dropTarget(for: ItemType1.self, withTitle: "Drop Zone 1")
InnerTestView(title: "Drop Zone 2", borderColor: .indigo)
.dropTarget(for: ItemType2.self, withTitle: "Drop Zone 2")
}
InnerTestView(title: "Drop Zone 3", borderColor: .orange)
.dropTarget(for: ItemType3.self, withTitle: "Drop Zone 3")
}
.contentShape(Rectangle())
.draggable(ItemType1(id: "Object 1"))
}
}
struct ContentView: View {
var body: some View {
ScrollView {
LazyVStack {
TestView()
TestView()
InnerTestView(title: "Draggable 2", borderColor: .orange)
.draggable(ItemType2(id: "Object 2"))
InnerTestView(title: "Draggable 3", borderColor: .indigo)
.draggable(ItemType3(id: "Object 3"))
}
.padding()
}
}
}
Transfer Representations:
import UniformTypeIdentifiers
import CoreTransferable
extension UTType {
static let itemType1 = UTType(exportedAs: "com.droptest.typeone")
static let itemType2 = UTType(exportedAs: "com.droptest.typetwo")
static let itemType3 = UTType(exportedAs: "com.droptest.typethree")
}
struct ItemType1: Codable, Transferable {
var id: String
public static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .itemType1)
}
}
struct ItemType2: Codable, Transferable {
var id: String
public static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .itemType2)
}
}
struct ItemType3: Codable, Transferable {
var id: String
public static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .itemType3)
}
}
Can someone please shed some light? I have an app that uses Core Data and CloudKit, up until the last version, I was able to sync data between devices but now after I added two new Entities for some reason it stopped syncing between devices.
Here is how I did the change:
Created a new Core Data container.
Added the new Entities and their Attributes
Tested the new Entities locally to be able to send the new schema to CloudKit.
Went to CloudKit and made sure that the new Entities and Attributes were reflected on the Developent database.
Deploy Schema Cahnges.
Went to the Production database to make sure the new schema was deployed; and it was, both databases look the same.
Testing:
Tested in the simulator and with a real device and everything syncs, even the new Entities.
If I download the app from the App Store on two different devices they do NOT sync.
Based on the procedure I'm describing above, is there any important step I may have missed when doing the migration?
I'm not sure if this is related to the syncing issue but after testing a few times, I no longer can turn the iCloud on, I get the following message when I try to turn iCloud Sync On.
CoreData: error: CoreData+CloudKit: -[NSCloudKitMirroringDelegate resetAfterError:andKeepContainer:]: <NSCloudKitMirroringDelegate: 0x282c488c0> - resetting internal state after error: Error Domain=NSCocoaErrorDomain Code=134410 "CloudKit setup failed because there is another instance of this persistent store actively syncing with CloudKit in this process." UserInfo={NSURL=file:///var/mobile/Containers/Data/Application/73F19BC7-4538-4098-85C7-484B36192CF3/Library/Application%20Support/CoreDataContainer.sqlite, NSLocalizedFailureReason=CloudKit setup failed because there is another instance of this persistent store actively syncing with CloudKit in this process., NSUnderlyingException=Illegal attempt to register a second handler for activity identifier com.apple.coredata.cloudkit.activity.setup.8D4C04F6-8040-445A-9447-E5646484521}
Any idea of what could be wrong and preventing the devices from syncing? Any idea or suggestion is welcome.
Thanks
Aloha Friends,
My app has been having problems since the latest Apple update, and I am not sure what the best way to fix it would be?
The front end UI is where we are having problems. Has anyone else experienced this? Any advice on the best/ fastest way to update?
Hello everyone. I have a small question about Weak Self. In the example below, I am doing a long process to the data I query with SwiftData. (For this reason, I do it in the background.) I don't know if there is a possibility of a memory leak when the view is closed because this process takes a long time.
import SwiftUI
import SwiftData
struct ActiveRegView: View {
@Query(filter: #Predicate<Registration> {
$0.activeRegistration
}, animation: .default) private var regs: [Registration]
@Environment(ViewModel.self) private var vm
@State private var totalParkingFee: Decimal = 0
var body: some View {
...
.onAppear {
var totalParkingFee = Decimal()
DispatchQueue.global(qos: .userInitiated).async {
for reg in regs {
totalParkingFee += vm.parkingFee(from: reg.entryRegistration.entryDate, to: .now, category: reg.category, isCustomPrice: reg.isCustomPrice)
}
DispatchQueue.main.async {
withAnimation {
totalParkingFee = totalParkingFee
}
}
}
}
}
}
I use ViewModel with @Environment, so I don't initilize ViewModel every time view is initilized. I know it's a simple question and I thank you in advance for your answers.
Hey, so I am working on a note taking part of my app and I have been working on allowing different inputs. I got the PKCanvas to work on the preview and on the simulator when it is the only thing on the window group. But when I use this View as part of the tool selection in my app, when the user selects the Canvas view it loads but scribbles don't work, only in the preview in Xcode. I don't know why this behavior is happening.
The flow in which the PKCanvasView appears is, user taps NavLink to see their notes, in the NotesView they tap to show the toolbar, toolbar has 4 types of input, text, images, audio, scribbles (all other inputs work accordingly), user taps scribbles the view loads correctly but doesn't allow them to draw.
I have set the drawingPolicy to anyInput
Im new to swiftui and I just tried implementing onMove into my project but for some reason it not only lags a lot when I attempt to move the item but the item then goes back to its original place any idea why?(Also my ItemModel does conform to Identifiable so it does have an id)
import SwiftUI
struct ListView: View {
@State private var itemModel : [ItemModel] = [
ItemModel(title: "This Is Item 1", isCompleted: false),
ItemModel(title: "This Is Item 2", isCompleted: true),
ItemModel(title: "This Is Item 3", isCompleted: false)
]
var body: some View {
VStack(spacing: 20) {
List{
ForEach(itemModel) { items in
Text(items.title)
}
.onDelete(perform: { indexSet in
itemModel.remove(atOffsets: indexSet)
})
.onMove(perform: { indices, newOffset in
itemModel.move(fromOffsets: indices, toOffset: newOffset)
})
}
.listStyle(.plain)
.toolbar {
ToolbarItem(placement: .topBarLeading){
EditButton()
}
ToolbarItem(placement: .topBarTrailing) {
NavigationLink(destination: AddItemsView(), label: {
Text("add")
})
}
}
Spacer()
}
.navigationTitle("To Do List ✏️")
}
}
Hey there,
I am currently developing a SwiftUI app for iPad.
However, I am currently experiencing a behavior, that I can not explain.
My code creates a user-management view. This all works fine if I run it on my iPad Pro with Apple-M-Chip. However, if I try to run the exact same code on an older A-Chip iPad Air (4th Generation), my app crashes.
I can recreate this behavior in the simulator on my Mac. When I run it on an iPad Pro Simulator, all works fine. When I run it in an iPad Air (4th Gen) simulator, the app becomes unresponsive.
Unfortunatley Xcode does not throw any error that I could use to identify the problem.
Currently I solved it with a work-around: As soon as I add a Spacer (even with a with of 0) the code works as expected and the UI is shown. As soon as I remove the Spacer, it crashes again.
HStack(alignment: .top){
VStack{
if(data.ui_user.id != -1){
HStack{
Text("Employee-No.:")
Spacer()
TextField("Employee-No.", value: $data.ui_user.id, format: .number).textFieldStyle(.roundedBorder).disabled(true).containerRelativeFrame(.horizontal, count: 10, span: 3, spacing: 0)
}
}
HStack{
Text("Family Name:")
Spacer()
TextField("Family Name", text: $data.ui_user.familyName).textFieldStyle(.roundedBorder).containerRelativeFrame(.horizontal, count: 10, span: 3, spacing: 0)
}
HStack{
Text("Given Name:")
Spacer()
TextField("Given Name", text: $data.ui_user.givenName).textFieldStyle(.roundedBorder).containerRelativeFrame(.horizontal, count: 10, span: 3, spacing: 0)
}
HStack{
Text("Role:")
Spacer()
Picker("Select role", selection: $data.selectedRole){
ForEach(0 ..< self.availableRoles.count, id:\.self){
Text(self.availableRoles[$0].name ?? "???").tag($0)
}
}.pickerStyle(.menu).containerRelativeFrame(.horizontal, count: 10, span: 3, spacing: 0).background(.drkBlueContainer).cornerRadius(5).foregroundColor(.drkOnBlueContainer).tint(.drkOnBlueContainer)
//If I add this spacer, it works. If not, it crashes
//Spacer().frame(width: 0.0)
}.disabled(noEditAdmin && data.ui_user.id != -1)
}
Divider()
VStack{
HStack{
Text("Created at:")
Spacer()
TextField("Created at", value: $data.ui_user.createdAt, format: .dateTime).textFieldStyle(.roundedBorder).disabled(true).containerRelativeFrame(.horizontal, count: 10, span: 3, spacing: 0)
}
HStack{
Toggle("Terminates:", isOn: $data.ui_user.expires)
.padding(.trailing, 2.0)
}
if(data.ui_user.expires){
HStack{
DatePicker("Terminates at:", selection: $data.ui_user.expiresAt, displayedComponents: .date).pickerStyle(.automatic)
}}
}
}
If anyone has any ideas, I would be very grateful for a hint :-)
The loop plays smoothly in audacity but when I run it in the device or simulator it clicks each loop at different intensities.
I config the session at App level:
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(.playback, mode: .default, options: [.mixWithOthers])
try audioSession.setActive(true)
} catch {
print("Setting category session for AVAudioSession Failed")
}
And then I made my method on my class:
func playSound(soundId: Int) {
let sound = ModelData.shared.sounds[soundId]
if let bundle = Bundle.main.path(forResource: sound.filename, ofType: "flac") {
let backgroundMusic = NSURL(fileURLWithPath: bundle)
do {
audioPlayer = try AVAudioPlayer(contentsOf:backgroundMusic as URL)
audioPlayer?.prepareToPlay()
audioPlayer?.numberOfLoops = -1 // for infinite times
audioPlayer?.play()
isPlayingSounds = true
} catch {
print(error)
}
}
}
Does anyone have any clue? Thanks!
PS: If I use AVQueuePlayer and repeat the item the click noise disappear (but its no use, because I would need to repeat it indefinitely without wasting memory), if I use AVLooper I get a silence between loops. All with the same sound. Idk :/
PS2: The same happens with ALAC files.
Hi, I am building a view containing a list of struct objects. I have implemented the onMove to allow the list rows to be moved together with the toolbar EditButton. This works but I have a problem as per title, even when the EditButton is not clicked, i.e., not in editing mode, I still can move the list rows by long pressing the rows. I have searched high and low for a solution but couldn't find one that works perfectly.
Below is the code snippet.
I am a new iOS dev and really appreciate if someone could help me.
Many thanks!
@AppStorage("fruits") private var fruits = Fruits.fruits
var body: some View {
NavigationStack {
VStack {
List {
ForEach(fruits) { fruit in
NavigationLink(destination: FruitsView(title: fruit.title)) {
fruit.icon
Text(fruit.title)
}
}
.onMove { fruits.move(fromOffsets: $0, toOffset: $1) }
}
.environment(\.defaultMinListRowHeight, UIElementConstants.listCellHeight)
}
.toolbar { EditButton() }
.navigationTitle("Fruits")
}
}
struct Fruits: Identifiable, Codable {
var id: UUID
var title: String
var iconName: String
var icon: Image {
Image(systemName: self.iconName)
}
init(id: UUID = UUID(), title: String, iconName: String) {
self.id = id
self.title = title
self.iconName = iconName
}
}
extension Fruits {
static let fruits: [Fruits] = [
Fruits(
title: "Apple",
iconName: "basket.fill"),
Fruits(
title: "Banana",
iconName: "basket.fill"),
Fruits(
title: "Pineapple",
iconName: "basket.fill")
]
}
Hi,
How to hide the Chevron icon from a list rows in SwiftUI ?
Kindest Regards
FB13955415
Calls to openURL in iOS 18 beta 22A5282m fails to launch browser.
Is this a bug?
How can I use availability checks with the new defaultWindowPlacement API so I can ensure visionOS 2 users get the correct window placement and visionOS 1 users fall back to the old window placement mechanism?
I have tried using if #available and @available in different ways, but end up with either an error that says control flow statements aren't allowed in SceneBuilders or that the return type of the two branches of my control flow statement do not match if I omit the SceneBuilder property.
An example of how to use .defaultWindowPlacement with visionOS 1 support would be nice. Thank you!
iOS app running in compatibility mode on Apple Vision Pro does not apply hoverEffect to SwiftUI View
We tested our iOS app on visionOS and found that the hover effect works on most of UIKit views, but it does not work on most of SwiftUI views.
SwiftUI views are used within UIHostingController.
I created a new project (Storyboard based iOS app) and displayed the following SwiftUI view in UIHostingController and found that the buttons in the List were highlighted, but not the standalone buttons.
struct SwiftUIView: View {
var body: some View {
List {
// This button has the hover effect.
Button {
print("Hello")
} label: {
Text("Hello")
}
}
// This button does't have the hover effect.
Button {
print("Hello")
} label: {
Text("Hello")
}
.hoverEffect()
}
}
Is there a way to highlight any SwiftUI view?
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.