I'm developing an app for my watch and have a list of different classes (referred to as 'courses' to avoid confusion) that I take in school, eg:
struct Course {
let name: String
let icon: String
let room: String
let colour: String
let listName: String
let listIcon: String
init(name: String, icon: String, room: String? = nil, colour: String,
listName: String? = nil, listIcon: String? = nil)
{
self.name = name
self.icon = icon
self.room = room ?? "None"
self.colour = colour
self.listName = listName ?? name
self.listIcon = listIcon ?? icon+".circle.fill"
}
}
// here's a few of my Course variables (there are lots more I've excluded)
let MathsCourse = Course(name: "Maths", icon: "number", room: "FT5", colour: "Rose")
let English6 = Course(name: "English", icon: "book.closed", room: "BT6", colour: "Lemon")
let LunchPeriod = Course(name: "Lunch", icon: "fork.knife", room: "food", colour: "White")
and I have designed a 'list view' of all the courses I have on whatever day it is. I used to define a different View for every course I defined but I merged the list data with the overall Course and replaced the Views with something that I should be able to call repeatedly and pass in a course to use, but unfortunately it's not working. (yes, I'm making a timetable app)
This is the template for a 'list view' for a class:
struct courseListView: View {
var localcourse: Course
var localtime: String
var body: some View {
HStack{
Image(systemName: localcourse.listIcon)
.foregroundColor(Color(localcourse.colour))
.padding(.leading, 5)
Text(localcourse.name)
.bold()
Spacer()
Text(localtime)
Text(roomOrBlank(course: localcourse)).bold().padding(.trailing, 5)
}
.padding(.bottom, 1)
.background(currentCourse.name==localcourse.name ? Color(localcourse.colour).colorInvert(): nil)
}
}
Then I should be able to programmatically work out what courses I have that day (I haven't scripted that bit yet), and compose a view containing all the courses:
struct ListView: View {
var body: some View {
ScrollView {
VStack(alignment: .leading) {
courseListView(localcourse: MathsCourse, localtime: "10:00")
// ^^^ note above line; I'll come back to this
// the idea is I can repeat that for a bunch of courses:
courseListView(localcourse: English6, localtime: "11:00")
courseListView(localcourse: LunchPeriod, localtime: "12:00")
}
}
}
}
Then be able to call all that in my @main:
@main
struct Timetaber_Watch_AppApp: App {
var body: some Scene {
WindowGroup {
TabView{
HomeView()
ListView()
SettingsView()
}
.tabViewStyle(.carousel)
.onAppear() {
log()
}
}
}
}
Unfortunately, each time I try to get a list view for a course,
// if you need a reminder:
courseListView(localcourse: MathsCourse, localtime: "10:00")
...even only calling it once causes my entire app to crash.
Here's an excerpt from the crash report:
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x0000000180210194
Termination Reason: SIGNAL 5 Trace/BPT trap: 5
Terminating Process: exc handler [14932]
Triggered by Thread: 0
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 libdispatch.dylib 0x180210194 _dispatch_once_wait.cold.1 + 28
1 libdispatch.dylib 0x1801db4f4 _dispatch_once_wait + 184
2 ??? 0x3400200d0 ???
3 ??? 0x340020198 ???
4 ...
So...
Anyone know what's happening?
Explore the various UI frameworks available for building app interfaces. Discuss the use cases for different frameworks, share best practices, and get help with specific framework-related questions.
Post
Replies
Boosts
Views
Activity
A simple view has misaligned localized content after being converted to an image using ImageRenderer.
This is still problematic on real phone and TestFlight
I'm not sure what the problem is, I'm assuming it's an ImageRenderer bug.
I tried to use UIGraphicsImageRenderer, but the UIGraphicsImageRenderer captures the image in an inaccurate position, and it will be offset resulting in a white border. And I don't know why in some cases it encounters circular references that result in blank images.
"(1) days" is also not converted to "1 day" properly.
In creating a sequenced gesture combining a LongPressGesture and a DragGesture, I found that the combined gesture exhibits two problems:
The @GestureState does not properly update as the gesture progresses through its phases. Specifically, the updating(_:body:) closure (documented here) is only ever executed during the drag interaction. Long presses and drag-releases do not call the updating(_:body:) closure.
Upon completing the long press gesture and activating the drag gesture, the drag gesture remains empty until the finger or cursor has moved. The expected behavior is for the drag gesture to begin even when its translation is of size .zero.
This second problem – the nonexistence of a drag gesture once the long press has completed – prevents access to the location of the long-press-then-drag. Access to this location is critical for displaying to the user that the drag interaction has commenced.
The below code is based on Apple's example presented here. I've highlighted the failure points in the code with // *.
My questions are as follows:
What is required to properly update the gesture state?
Is it possible to have a viable drag gesture immediately upon fulfilling the long press gesture, even with a translation of .zero?
Alternatively to the above question, is there a way to gain access to the location of the long press gesture?
import SwiftUI
import Charts
enum DragState {
case inactive
case pressing
case dragging(translation: CGSize)
var isDragging: Bool {
switch self {
case .inactive, .pressing:
return false
case .dragging:
return true
}
}
}
struct ChartGestureOverlay<Value: Comparable & Hashable>: View {
@Binding var highlightedValue: Value?
let chartProxy: ChartProxy
let valueFromChartProxy: (CGFloat, ChartProxy) -> Value?
let onDragChange: (DragState) -> Void
@GestureState private var dragState = DragState.inactive
var body: some View {
Rectangle()
.fill(Color.clear)
.contentShape(Rectangle())
.onTapGesture { location in
if let newValue = valueFromChartProxy(location.x, chartProxy) {
highlightedValue = newValue
}
}
.gesture(longPressAndDrag)
}
private var longPressAndDrag: some Gesture {
let longPress = LongPressGesture(minimumDuration: 0.2)
let drag = DragGesture(minimumDistance: .zero)
.onChanged { value in
if let newValue = valueFromChartProxy(value.location.x, chartProxy) {
highlightedValue = newValue
}
}
return longPress.sequenced(before: drag)
.updating($dragState) { value, gestureState, _ in
switch value {
case .first(true):
// * This is never called
gestureState = .pressing
case .second(true, let drag):
// * Drag is often nil
// * When drag is nil, we lack access to the location
gestureState = .dragging(translation: drag?.translation ?? .zero)
default:
// * This is never called
gestureState = .inactive
}
onDragChange(gestureState)
}
}
}
struct DataPoint: Identifiable {
let id = UUID()
let category: String
let value: Double
}
struct ContentView: View {
let dataPoints = [
DataPoint(category: "A", value: 5),
DataPoint(category: "B", value: 3),
DataPoint(category: "C", value: 8),
DataPoint(category: "D", value: 2),
DataPoint(category: "E", value: 7)
]
@State private var highlightedCategory: String? = nil
@State private var dragState = DragState.inactive
var body: some View {
VStack {
Text("Bar Chart with Gesture Interaction")
.font(.headline)
.padding()
Chart {
ForEach(dataPoints) { dataPoint in
BarMark(
x: .value("Category", dataPoint.category),
y: .value("Value", dataPoint.value)
)
.foregroundStyle(highlightedCategory == dataPoint.category ? Color.red : Color.gray)
.annotation(position: .top) {
if highlightedCategory == dataPoint.category {
Text("\(dataPoint.value, specifier: "%.1f")")
.font(.caption)
.foregroundColor(.primary)
}
}
}
}
.frame(height: 300)
.chartOverlay { chartProxy in
ChartGestureOverlay<String>(
highlightedValue: $highlightedCategory,
chartProxy: chartProxy,
valueFromChartProxy: { xPosition, chartProxy in
if let category: String = chartProxy.value(atX: xPosition) {
return category
}
return nil
},
onDragChange: { newDragState in
dragState = newDragState
}
)
}
.onChange(of: highlightedCategory, { oldCategory, newCategory in
})
}
.padding()
}
}
#Preview {
ContentView()
}
Thank you!
In my code, I do this:
Text("\(languagesManager.availableWords.count)")
And next time I build, this creates an entry in Localizable.strings: %lld
Is there a way I can flag this UI element to indicate its string doesn't need to be localized?
https://developer.apple.com/documentation/mapkit/mkgeojsondecoder?changes=__9&language=objc
I am trying to use this decoder to obtain single points form a geojson file. I am able to do this successfully however, when using MapKit for iOS 17+ I am unable to use a ForEach to iterate through these points (stored in an array) and display these on the map as a custom annotation or even a marker.
MapAnnotation(coordinate: point.coordinate) {
VStack {
Image(systemName: "mappin.circle.fill")
.resizable()
.frame(width: 25, height: 25)
.foregroundColor(.purple)
if let title = point.title {
Text(title)
.font(.caption)
.foregroundColor(.purple)
.padding(2)
.background(Color.white.opacity(0.8))
.cornerRadius(3)
}
}
}
I am running into an issue where two distinct bool bindings are both being toggled when I toggle only one of them. My component looks like
VStack {
Checkbox(label: "Checkbox 1", isOn: $stateVar1)
Checkbox(label: "Checkbox 2", isOn: $stateVar2)
}
where my CheckBox component looks like
struct Checkbox: View {
let label: String
@Binding var isOn: Bool
var body: some View {
Button {
isOn.toggle()
} label: {
HStack {
Image(systemName: isOn ? "checkmark.square" : "square")
Text(label)
}
.foregroundStyle(.black)
}
}
}
If I click on one checkbox, both of them get toggled. However, if I simply remove the checkboxes from the VStack, then I am able to toggle them both independently. I believe this is a bug with bool Bindings, but if anyone can point out why I am mistaken that would be much appreciated :)
I have a complex app that requires the main SwiftUI view of the app to be embedded inside an NSHostingView which is a subview of an NSViewController's view. Then this NSViewController is wrapped using NSViewControllerRepresentable to be presented using SwiftUI's Window. And if I have a TimelineView inside my SwiftUI view hierarchy, it causes constant recalculation of the layout.
Here's a simplified demo code:
@main
struct DogApp: App {
private let dogViewController = DogViewController()
var body: some Scene {
Window("Dog", id: "main") {
DogViewControllerUI()
}
}
}
private struct DogViewControllerUI: NSViewControllerRepresentable {
let dogViewController = DogViewController ()
func makeNSViewController(context: Context) -> NSViewController { dogViewController }
func updateNSViewController(_ nsViewController: NSViewController, context: Context) {}
func sizeThatFits(_ proposal: ProposedViewSize, nsViewController: NSViewController, context: Context) -> CGSize? {
debugPrint("sizeThatFits", proposal)
return nil
}
}
public class DogViewController: NSViewController {
public override func viewDidLoad() {
super.viewDidLoad()
let mainView = MainView()
let hostingView = NSHostingView(rootView: mainView)
view.addSubview(hostingView)
hostingView.translatesAutoresizingMaskIntoConstraints = false
hostingView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
hostingView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
hostingView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
hostingView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
}
struct MainView: View {
var body: some View {
VStack {
TimelineView(.animation) { _ in
Color.random
.frame(width: 100, height: 100)
}
}
}
}
extension Color {
static var random: Color {
Color(
red: .random(in: 0...1),
green: .random(in: 0...1),
blue: .random(in: 0...1)
)
}
}
When running it's printing out this repeatedly (multiple times a second).
"sizeThatFits" SwiftUI.ProposedViewSize(width: Optional(559.0), height: Optional(528.0))
"sizeThatFits" SwiftUI.ProposedViewSize(width: Optional(0.0), height: Optional(0.0))
"sizeThatFits" SwiftUI.ProposedViewSize(width: Optional(559.0), height: Optional(528.0))
"sizeThatFits" SwiftUI.ProposedViewSize(width: Optional(0.0), height: Optional(0.0))
"sizeThatFits" SwiftUI.ProposedViewSize(width: Optional(559.0), height: Optional(528.0))
"sizeThatFits" SwiftUI.ProposedViewSize(width: Optional(0.0), height: Optional(0.0))
"sizeThatFits" SwiftUI.ProposedViewSize(width: Optional(559.0), height: Optional(528.0))
If I run an equivalent code for an iPad, it only prints twice. If I comment out TimelineView on macOS, then it only prints out the above logs when resizing the app window.
The main reason this is an issue is that it's clearly causing dramatic degradation in performance. I was told to submit a bug report after I submitted TSI so a SwiftUI engineer could investigate it. Case-ID: 7461887. FB13810482. This was back in May but I received no response. LLMs are no help, and I've experimented with all sorts of workarounds. My last hope is this forum, maybe someone has an idea of what might be going on and why the recalculation is happening constantly on macOS.
Below is a basic test app to resemble an actual app I am working on to hopefully better describe an issue I am having with tab view. It seems only in split screen when I am triggering something onAppear that would cause another view to update, or another view updates on its own, the focus gets pulled to that newly updated view instead of staying on the view you are currently on. This seems to only happen with views that are listed in the more tab. In any other orientation other than 50/50 split this does not happen. Any help would be appreciated.
struct ContentView: View {
@State var selectedTab = 0
var body: some View {
NavigationStack {
NavigationLink(value: 0) {
Text("ENTER")
}.navigationDestination(for: Int.self) { num in
TabsView(selectedTab: $selectedTab)
}
}
}
}
struct TabsView: View {
@Binding var selectedTab: Int
@State var yikes: Int = 0
var body: some View {
if #available(iOS 18.0, *) {
TabView(selection: $selectedTab) {
MyFlightsView(yikes: $yikes)
.tabItem {
Label("My Flights", systemImage: "airplane.circle")
}.tag(0)
FlightplanView()
.tabItem {
Label("Flight Plan", systemImage: "doc.plaintext")
}.tag(1)
PreFlightView()
.tabItem {
Label("Pre Flight", systemImage: "airplane.departure")
}.tag(2)
CruiseView(yikes: $yikes)
.tabItem {
Label("Cruise", systemImage: "airplane")
}.tag(3)
PostFlightView()
.tabItem {
Label("Post Flight", systemImage: "airplane.arrival")
}.tag(4)
MoreView()
.tabItem {
Label("More", systemImage: "ellipsis")
}.tag(5)
NotificationsView()
.tabItem {
Label("Notifications", systemImage: "bell")
}.tag(6)
}.tabViewStyle(.sidebarAdaptable)
}
}
}
I have an attributedString with 100 NSTextAttachments(contains image of 400kb). When i scroll the textview, it is lagging, When i did the same in textkit 1, it is butter smooth. It can be because of how textkit 1 & 2 layout the elements.
let attachment = NSTextAttachment()
attachment.image = UIImage(named: "image2")
let attachmentString = NSAttributedString(attachment: attachment)
let mutableAttributedString = NSMutableAttributedString(attributedString: textView.attributedText)
for _ in 0...100 {
mutableAttributedString.append(NSAttributedString(string: "\n"))
mutableAttributedString.append(attachmentString)
}
textView.attributedText = mutableAttributedString
How to handle images in textkit 2 so that it feels smooth while scrolling textview?
A specific image fails to load properly using UIImageView on iOS 16 and later systems, but loads normally on iOS 15 and earlier versions. Similarly, on Mac computers, this image cannot be opened on MacOS 13 and later, whereas it opens without issue on MacOS 12 and earlier. I am curious about the reasons behind this differing behavior on both iPhone and Mac.
Xcode 16.1
iOS 18.1
iPad Air 13-inch (M2)
I have created a completely new Xcode project with Swift and Storyboard.
In Storyboard, I only have a tabBarController with two attached UIViewControllers.
The code from the base project hasn't been changed at all.
I created a UITest with a very simple premise.
let app = XCUIApplication()
override func setupWithError() throws {
continueAfterFailure = false
app.launch()
}
func testTabBarExistence() {
let tabBar = app.tabBars.element(boundBy: 0)
XCTAssertTrue(tabBar.waitForExistence(timeout: 5), "Tab bar should exist")
}
But this test always fails for iPad on iOS 18+, but it will succeed for anything lower (e.g. 17.5)
Dear Sirs,
I'm writing an audio application that should show up to 128 horizontal peakmeters (width for each is about 150, height is 8) stacked inside a ScrollViewReader. For the actual value of the peakmeter I have a binding to a CGFloat value. The peakmeter works as expected and is refreshing correct. For testing I added a timer to my swift application that is firing every 0.05 secs, meaning I want to show 20 values per second. Inside the timer func I'm just creating random CGFloat values in range of 0...1 for the bound values. The peakmeters refresh and flicker as expected but I can see a CPU load of 40-50% in the activity monitor on my MacBook Air with Apple M2 even when compiled in release mode. I think this is quite high and I'd like to reduce this CPU load. Should this be possible? I.e. I thought about blocking the refresh until I've set all values? How could this be done and would it help? What else could I do?
Thanks and best regards,
JFreyberger
on iOS you can choose to scale to view to have the app resize the screen easily in the developer environment. Scale to view is however not easily done on MacOS using NS to solve on MacOS now. Is it possible for the Apple developer team to make this easier for the Developer, as I understand it is for iOS applications?
I added a background view to my SwiftUI List, and would like to move it up as user scrolls (similar to the effect of that of the Health app). I can't add it onto the background of a List row, because List unconditionally clips content to a row, and I would like the view to extend past a insetted row's bounds (scrollClipDisabled does not work on List). So I added the view as the background view of the entire List.
Currently, I am achieving this by monitoring contentOffset using the new onScrollGeometryChange(for:of:action:) modifier, updating a state variable that controls the offset of the background view. The code looks something like this:
struct ContentView: View {
@State private var backgroundOffset: CGFloat = 0
var body: some View {
List {
...
}
.background {
backgroundView
.offset(y: backgroundOffset)
}
.onScrollGeometryChange(for: ScrollGeometry.self) { geometry in
geometry
} action: { oldValue, newValue in
let contentOffsetY = newValue.contentOffset.y
let contentInsetY = newValue.contentInsets.top
if contentOffsetY <= -contentInsetY {
backgroundOffset = 0
} else {
backgroundOffset = -(contentOffsetY + contentInsetY)
}
}
}
}
However, this results in bad scrolling performance. I am guessing this is due to backgroundOffset being updated too frequently, and thus refreshing the views too often? If so, what is a more performant approach to achieve the desired effect? Thanks!
(NOTE: In sum, this is destructive of user data.)
The client is a professor of Classics in constant need of properly-rendered glyphs that represent legitimate code points. As an example, the correct spelling might be:
εὔτρητος
It is spelled and rendered as intended. A file by this name will be correctly spelled by ls in the Terminal. Note that two diacritics are applied to the second letter, an upsilon (ὔ)
However, the Finder displays that file as
ἐύτρητος
and iterating the string reveals that the accents are improperly distributed over the two. This would never be correct.
This handicaps digital-humanities researchers from college to postdoctoral work.
A Character by Character iteration demonstrates the mangling.:
intended (εὔτρητος)
displayed (ἐύτρητος)
3B5 (ε) 1F10 (ἐ)
GREEK SMALL LETTER EPSILON,
GREEK SMALL LETTER EPSILON WITH PSILI
1F54 (ὔ) 3CD (ύ)
GREEK SMALL LETTER UPSILON WITH PSILI AND OXIA
GREEK SMALL LETTER UPSILON WITH TONOS
3C4 (τ) 3C4 (τ)
(back in sync)
3C1 (ρ) 3C1 (ρ)
3B7 (η) 3B7 (η)
3C4 (τ) 3C4 (τ)
3BF (ο) 3BF (ο)
3C2 (ς) 3C2 (ς)
I don't want to muddy the waters by guessing where and how the mistake is made, just see for yourself.
It looks like Xcode 16 has changed this behaviour so I'm not sure if this is a bug or not.
When a SwiftUI Button wraps a UIImageView and the button style is .plain the button doesn't work without setting isUserInteractionEnabled.
struct ContentView: View {
var body: some View {
Button {
print("Hello World!")
} label: {
UITestImage()
}
.buttonStyle(.plain)
}
}
struct UITestImage: UIViewRepresentable {
func makeUIView(context: Context) -> UIImageView {
let view = UIImageView()
// view.isUserInteractionEnabled = true // Fix
view.image = UIImage(systemName: "plus")
view.contentMode = .scaleAspectFit
view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
view.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
view.layoutMargins = .zero
return view
}
public func updateUIView(_ uiView: UIImageView, context: Context) {}
}
This feels unexpected, is this a bug?
At this line of code (SketchTextSelectionManager.swift:449), sometimes there will be crashes based on crashlytics reports.
let selection = pdfPage.selectionForWord(at: location)
This is directly calling into PDFKit's PDFPage#selection method: https://developer.apple.com/documentation/pdfkit/pdfpage/selectionforword(at:)
Attached the full stacktrace:
Crashed: com.apple.root.user-initiated-qos.cooperative
0 CoreGraphics 0x30c968 PageLayout::getWordRange(unsigned long, long) const + 908
1 CoreGraphics 0x30bbc0 PageLayout::getTextRangeIndex(CGPoint, CGPDFSelectionType, SelectionPrecision) const + 2292
2 CoreGraphics 0x44a53c CGPDFSelectionCreateBetweenPointsWithOptions + 384
3 PDFKit 0x8d5f8 -[PDFPage selectionFromPoint:toPoint:type:] + 168
4 PDFKit 0x92040 -[PDFPage _rvItemAtPoint:] + 64
5 PDFKit 0x91f4c -[PDFPage rvItemAtPoint:] + 84
6 PDFKit 0x8caa8 -[PDFPage selectionForWordAtPoint:] + 40
7 MyApp 0x8420e0 closure #1 in SketchTextSelectionManager.startNewTextSelection(pageId:location:) + 449 (SketchTextSelectionManager.swift:449)
8 MyApp 0x841a70 SketchTextSelectionManager.startNewTextSelection(pageId:location:) + 205 (CurrentNoteManager.swift:205)
9 libswift_Concurrency.dylib 0x61104 swift::runJobInEstablishedExecutorContext(swift::Job*) + 252
10 libswift_Concurrency.dylib 0x63a28 (anonymous namespace)::ProcessOutOfLineJob::process(swift::Job*) + 480
11 libswift_Concurrency.dylib 0x611c4 swift::runJobInEstablishedExecutorContext(swift::Job*) + 444
12 libswift_Concurrency.dylib 0x62514 swift_job_runImpl(swift::Job*, swift::SerialExecutorRef) + 144
13 libdispatch.dylib 0x15d8c _dispatch_root_queue_drain + 392
14 libdispatch.dylib 0x16590 _dispatch_worker_thread2 + 156
15 libsystem_pthread.dylib 0x4c40 _pthread_wqthread + 228
16 libsystem_pthread.dylib 0x1488 start_wqthread + 8
```
At this line of code (SketchTextSelectionManager.swift:378), sometimes there will be crashes based on crashlytics reports. In the reports, it seems like this only happens for RTL text range.
let selection = pdfPage.selection(
from: CGPoint(x: fromStart.x + 1, y: fromStart.y - 1),
to: CGPoint(x: toEnd.x - 1, y: toEnd.y + 1)
)
This is directly calling into PDFKit's PDFPage#selection method: https://developer.apple.com/documentation/pdfkit/pdfpage/selection(from:to:)
Attached the full stacktrace:
Crashed: com.apple.root.user-initiated-qos.cooperative
0 CoreGraphics 0x30598c PageLayout::convertRTLTextRangeIndexToStringRangeIndex(long) const + 156
1 CoreGraphics 0x44c3f0 CGPDFSelectionCreateBetweenPointsWithOptions + 224
2 PDFKit 0x91d00 -[PDFPage selectionFromPoint:toPoint:type:] + 168
3 MyApp 0x841044 closure #1 in SketchTextSelectionManager.handleUserTouchMoved(_:) + 378 (SketchTextSelectionManager.swift:378)
4 MyApp 0x840cb0 SketchTextSelectionManager.handleUserTouchMoved(_:) + 205 (CurrentNoteManager.swift:205)
5 libswift_Concurrency.dylib 0x60f5c swift::runJobInEstablishedExecutorContext(swift::Job*) + 252
6 libswift_Concurrency.dylib 0x63a28 (anonymous namespace)::ProcessOutOfLineJob::process(swift::Job*) + 480
7 libswift_Concurrency.dylib 0x6101c swift::runJobInEstablishedExecutorContext(swift::Job*) + 444
8 libswift_Concurrency.dylib 0x62514 swift_job_runImpl(swift::Job*, swift::SerialExecutorRef) + 144
9 libdispatch.dylib 0x15ec0 _dispatch_root_queue_drain + 392
10 libdispatch.dylib 0x166c4 _dispatch_worker_thread2 + 156
11 libsystem_pthread.dylib 0x3644 _pthread_wqthread + 228
12 libsystem_pthread.dylib 0x1474 start_wqthread + 8
I'm working on an app targeting iOS 15+ using SwiftUI.
The app has several Views that load data from an API in their onAppear() method. While the loading operation is in progress, these views show a loading overlay via .fullScreenCover().
While most of the time this works as expected, I've discovered that if the API operation completes before the overlay's .onAppear() has fired, the overlay gets stuck on screen, i.e. does not dismiss. This bug occurs both in the simulator and on device.
This is a simplified version of my implementation:
struct MyDataView: View {
@EnvironmentObject var store:Store
var Content: some View {
// ...
}
@ViewBuilder
var body: some View {
let showLoadingOverlay = Binding(
get: {
store.state.loading
},
set: { _ in }
)
Content
.onAppear {
store.dispatch(LoadData)
}
.fullScreenCover(isPresented: showLoadingOverlay) {
LoadingOverlay()
}
}
}
Log messages tell me that my store is updating correctly, i.e. the booleans all operate as expected. Adding log output to the binding's getter always prints the correct value. Adding a breakpoint to the binding's getter makes the problem disappear.
I've found that the chronology of events that lead to this bug is:
MyDataView.onAppear()
LoadData
Binding: true
Overlay starts animating in
LoadData finishes
Binding: false
Overlay fires it's onAppear
I.e. whenever loading finishes before the fullScreenCover's onAppear is fired, the overlay get's stuck on screen. As long as loading takes at least as long as it takes the overlay to appear, the bug does not occur.
It appears to be a race condition between the .fullScreenCover appearing and the binding changing to false.
I've found that the bug can be avoided if loading is triggered in the overlay's .onAppear(). However, I would like to avoid this workaround because the overlay is not supposed to carry out data loading tasks.
I have a SwiftUI LineMark chart that inverts the y axis when the data the chart is plotting is all zeros. I'm expecting the y axis 0 to be at the bottom but when the data is all zeros it's at the top. Below is an example demonstrating the problem:
import SwiftUI
import Charts
struct ChartView: View {
let data: [Double] = [0,0,0,0,0]
var body: some View {
Chart {
ForEach(data.indices, id: \.self) { index in
LineMark(
x: .value("Index", index),
y: .value("Value", data[index])
)
}
}
.chartYAxis {
AxisMarks(values: [0, 10, 20, 30, 40, 50]) { value in
AxisValueLabel()
AxisTick()
AxisGridLine()
}
}
.padding()
}
}
I can't use .chartYScale(domain: ) because it overrides the chartYAxis where the real code creates custom a leading and trailing y axis.
Does anyone have any suggestions how I may fix this?