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!
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
Hi everyone! I’m fairly new to Swift and currently working on a small iOS app in SwiftUI. The app is able to load a CSV file embedded in the Xcode project (using Bundle.main.path(forResource:)), and everything works well with that.
Now, I want to take it a step further by allowing the app to load an external CSV file located in the iPhone’s directories (like “Documents” or “Downloads”). However, I’m struggling to make it work. I tried using a DocumentPicker to select the CSV file, and I believe I’m passing the file URL correctly, but the app keeps reading only the embedded file instead of the one selected by the user.
Could anyone offer guidance on how to properly set up loading an external CSV file? I’m still learning, so any suggestions or examples would be really appreciated!
Thanks a lot in advance for the help!
Here’s the code that isn’t working as expected:
import Foundation
struct Product: Identifiable {
let id = UUID()
var codice: String
var descrizione: String
var prezzo: Double
var installazione: Double
var trasporto: Double
}
class ProductViewModel: ObservableObject {
@Published var products: [Product] = []
@Published var filteredProducts: [Product] = []
func loadCSV(from url: URL) {
products = []
do {
let data = try String(contentsOf: url)
let lines = data.components(separatedBy: "\n")
// Legge e processa ogni riga del CSV (saltando la prima riga se è l'intestazione)
for line in lines.dropFirst() {
let values = line.components(separatedBy: ";")
// Assicurati che ci siano abbastanza colonne e gestisci i valori mancanti
if values.count >= 5 {
let codice = values[0].trimmingCharacters(in: .whitespaces)
let descrizione = values[1].trimmingCharacters(in: .whitespaces)
let prezzo = parseEuropeanDouble(values[2]) ?? 0.0
let installazione = parseEuropeanDouble(values[3].isEmpty ? "0,00" : values[3]) ?? 0.0
let trasporto = parseEuropeanDouble(values[4].isEmpty ? "0,00" : values[4]) ?? 0.0
let product = Product(
codice: codice,
descrizione: descrizione,
prezzo: prezzo,
installazione: installazione,
trasporto: trasporto
)
products.append(product)
}
}
filteredProducts = products
} catch {
print("Errore nel caricamento del CSV: \(error)")
}
}
private func parseEuropeanDouble(_ value: String) -> Double? {
let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "it_IT")
formatter.numberStyle = .decimal
return formatter.number(from: value)?.doubleValue
}
}
struct ContentView: View {
@StateObject var viewModel = ProductViewModel()
@State private var showFilePicker = false
var body: some View {
VStack {
Button("Carica file CSV") {
showFilePicker = true
}
.fileImporter(isPresented: $showFilePicker, allowedContentTypes: [.commaSeparatedText]) { result in
switch result {
case .success(let url):
viewModel.loadCSV(from: url)
case .failure(let error):
print("Errore nel caricamento del file: \(error.localizedDescription)")
}
}
List(viewModel.filteredProducts) { product in
VStack(alignment: .leading) {
Text("Codice: \(product.codice)")
Text("Descrizione: \(product.descrizione)")
Text("Prezzo Lordo: €\(String(format: "%.2f", product.prezzo))")
Text("Installazione: €\(String(format: "%.2f", product.installazione))")
Text("Trasporto: €\(String(format: "%.2f", product.trasporto))")
}
}
}
.padding()
}
}
Hello, since the last version of iOS and WatchOS I have a problem with this code.
This is the minimal version of the code, it have two pickers inside a view of a WatchOS App.
The problem its with the focus, I can't change the focus from the first picker to the second one.
As I said before, it was working perfectly in WatchOS 10.0 but in 11 the problems started.
struct ParentView: View {
@FocusState private var focusedField: String?
var body: some View {
VStack {
ChildView1(focusedField: $focusedField)
ChildView2(focusedField: $focusedField)
}
}
}
struct ChildView1: View {
@FocusState.Binding var focusedField: String?
@State private var selectedValue: Int = 0
var body: some View {
Picker("First Picker", selection: $selectedValue) {
ForEach(0..<5) { index in
Text("Option \(index)").tag("child\(index)")
}
}.pickerStyle(WheelPickerStyle()).focused($focusedField, equals: "first")
}
}
struct ChildView2: View {
@FocusState.Binding var focusedField: String?
@State private var selectedValue: Int = 0
var body: some View {
Picker("Second Picker", selection: $selectedValue) {
ForEach(0..<5) { index in
Text("Option \(index)").tag("childTwo\(index)")
}
}.pickerStyle(WheelPickerStyle()).focused($focusedField, equals: "second")
}
}
When you do vertical scrolling on the second picker, the focus should be on it, but it dosnt anything.
I try even do manually, setting the focusState to the second one, but it sets itself to nil.
I hope that you can help me, thanks!
Is there a way to make UITextField activate when double-tapped? Single-tapping makes it a little too easy to do something calamitous in my app.
I just tried the "Building a document-based app with SwiftUI" sample code for iOS 18.
https://developer.apple.com/documentation/swiftui/building-a-document-based-app-with-swiftui
I can create a document and then close it. But once I open it back up, I can't navigate back to the documents browser. It also struggles to open documents (I would tap multiple times and nothing happens). This happens on both simulator and device.
Will file a bug, but anyone know of a work-around? I can't use a document browser that is this broken.
I'm looking at a case where a handler for NSWindowDidBecomeMain gets the NSWindow* from the notification object and verifies that window.isVisible == YES, window.windowNumber > 0 and window.screen != nil. However, window.windowNumber is missing from the array [NSWindow windowNumbersWithOptions: NSWindowNumberListAllSpaces] and from CGWindowListCopyWindowInfo( kCGWindowListOptionOnScreenOnly, kCGNullWindowID ), how can that be?
The window number is in the array returned by CGWindowListCopyWindowInfo( kCGWindowListOptionAll, kCGNullWindowID ).
I'm seeing this issue in macOS 15, maybe 14, but not 13.
Hi,
WWDC24 videos have a lot of references to an "Image Playground" API, and the "What's New in AppKit" session even shows it in action, with a "ImagePlaygroundViewController". However, there doesn't seem to be any access to the new API, even with Xcode 16.2 beta. Am I missing something, or is that 'coming later'?
I have an immersive space with a RealityKit view which is running an ARKitSession to access main camera frames.
This frame is processed with custom computer vision algorithms (and deep learning models).
There is a 3D Entity in the RealityKit view which I'm trying to place in the world, but I want to debug my (2D) algorithms in an "attached" view (display images in windows).
How to I send/share data or variables between the views (and and spaces)?
I am making a swift app supporting multi language ,showing proper language ui according user's phone setting language, I want to launch different screen (showing different image, boot-en.jpg, boot-ja.jpg) according language,i created two LaunchScreen files ,LaunchScreen-en.storyboard and LaunchScreen-ja.storyboard and localize them ,and add a different UIImage to them,
then create two InfoPlist.strings file with congfiging
."UILaunchStoryboardName" = "LaunchScreen_en"; //
"UILaunchStoryboardName" = "LaunchScreen_ja";//
and then **config info.plist ** with
UILaunchStoryboardName
LaunchScreen
above all steps ,build and run,hope to see launch screen showing boot-ja.jpg when phone's language is Japanese, showing boot-en.jpg when phone's language is English, but it shows black screen, how to fix this problem, thank you.
I would like to show a nswindow at a position on screen base on height of the nswindow and its content view. I received zero width and height on macOS 15.0 and xCode 16.1 however they were returned correct width and height on previous macOS version.
import Cocoa
import SwiftUI
class AppDelegate: NSObject, NSApplicationDelegate {
private var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
window = NSWindow(
contentRect: .zero,
styleMask: [.miniaturizable, .closable, .resizable],
backing: .buffered, defer: false)
window.title = "No Storyboard Window"
window.contentView = NSHostingView(rootView: ContentView()) // a swiftui view
window.center()
let windowFrame = window.frame
print("window Frame \(windowFrame)") // print width and height zero here
window.makeKeyAndOrderFront(nil)
}
}
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}
I tried window.layoutIfNeeded() after setting contentview but it didn't work
How can I get the frame after setting contentview for nswindow on macOS 15.0?
0 CoreFoundation 0x0000000183f687cc ___exceptionPreprocess + 164
1 libobjc.A.dylib 0x000000018123b2e4 _objc_exception_throw + 88
2 CoreFoundation 0x000000018406e5f0 +[NSObject(NSObject) doesNotRecognizeSelector:] + 0
3 UIKitCore 0x0000000186849a48 -[UIButtonLegacyVisualProvider _newLabelWithFrame:] + 60
4 UIKitCore 0x00000001867652b0 -[UIButtonLegacyVisualProvider _setupTitleViewRequestingLayout:] + 84
5 UIKitCore 0x0000000186763ba4 -[UIButtonLegacyVisualProvider titleViewCreateIfNeeded:] + 44
6 UIKitCore 0x00000001867d3f74 -[UIButton titleLabel] + 36
Hello bro, in IOS 18 our team find issue ,We created a button like this:
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.titleLabel.font = [UIFont fontWithName:paramFontName size: fontSize];
Please tell me whether we need to set button frame before we call button.titleLabe?
As of macOS Sequoia 15.1 (and probably earlier), in System Settings under Accessibility -> Display, there's a Text Size option that looks an awful lot like Dynamic Type on iOS:
I have an iOS app with robust support for Dynamic Type that I've brought to the Mac via Catalyst. Is there any way for me to opt this app into supporting this setting, maybe with some Info.plist key?
Calendar's Info.plist has a CTIgnoreUserFonts value set to true, but the Info.plist for Notes has no such value.
I would like to implement the same kind of behavior as the Dropoverapp application, specifically being able to perform a specific action when files are dragged (such as opening a window, for example).
I have written the code below to capture the mouse coordinates, drag, drop, and click globally. However, I don't know how to determine the nature of what is being dropped. Do you have any ideas on how to detect the nature of what is being dragged outside the application's scope?
Here is my current code:
import SwiftUI
import CoreGraphics
struct ContentView: View {
@State private var mouseX: CGFloat = 0.0
@State private var mouseY: CGFloat = 0.0
@State private var isClicked: Bool = false
@State private var isDragging: Bool = false
private var mouseTracker = MouseTracker.shared
var body: some View {
VStack {
Text("Mouse coordinates: \(mouseX, specifier: "%.2f"), \(mouseY, specifier: "%.2f")")
.padding()
Text(isClicked ? "Mouse is clicked" : "Mouse is not clicked")
.padding()
Text(isDragging ? "Mouse is dragging" : "Mouse is not dragging")
.padding()
}
.frame(width: 400, height: 200)
.onAppear {
mouseTracker.startTracking { newMouseX, newMouseY, newIsClicked, newIsDragging in
self.mouseX = newMouseX
self.mouseY = newMouseY
self.isClicked = newIsClicked
self.isDragging = newIsDragging
}
}
}
}
class MouseTracker {
static let shared = MouseTracker()
private var eventTap: CFMachPort?
private var runLoopSource: CFRunLoopSource?
private var isClicked: Bool = false
private var isDragging: Bool = false
private var callback: ((CGFloat, CGFloat, Bool, Bool) -> Void)?
func startTracking(callback: @escaping (CGFloat, CGFloat, Bool, Bool) -> Void) {
self.callback = callback
let mask: CGEventMask = (1 << CGEventType.mouseMoved.rawValue) |
(1 << CGEventType.leftMouseDown.rawValue) |
(1 << CGEventType.leftMouseUp.rawValue) |
(1 << CGEventType.leftMouseDragged.rawValue)
// Pass 'self' via 'userInfo'
let selfPointer = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
guard let eventTap = CGEvent.tapCreate(
tap: .cghidEventTap,
place: .headInsertEventTap,
options: .defaultTap,
eventsOfInterest: mask,
callback: MouseTracker.mouseEventCallback,
userInfo: selfPointer
) else {
print("Failed to create event tap")
return
}
self.eventTap = eventTap
runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
CGEvent.tapEnable(tap: eventTap, enable: true)
}
// Static callback function
private static let mouseEventCallback: CGEventTapCallBack = { _, eventType, event, userInfo in
guard let userInfo = userInfo else {
return Unmanaged.passUnretained(event)
}
// Retrieve the instance of MouseTracker from userInfo
let tracker = Unmanaged<MouseTracker>.fromOpaque(userInfo).takeUnretainedValue()
let location = NSEvent.mouseLocation
// Update the click and drag state
switch eventType {
case .leftMouseDown:
tracker.isClicked = true
tracker.isDragging = false
case .leftMouseUp:
tracker.isClicked = false
tracker.isDragging = false
case .leftMouseDragged:
tracker.isDragging = true
default:
break
}
// Call the callback on the main thread
DispatchQueue.main.async {
tracker.callback?(location.x, location.y, tracker.isClicked, tracker.isDragging)
}
return Unmanaged.passUnretained(event)
}
}
Hi,
When using SwiftUI ‘List’ with a large number of elements (4000+), I noticed a significant performance issue if extracting the views inside the ‘ForEach’ block into their own subview class. It affects scrolling performance, and using the scroll handle in the scrollbar causes stutters and beachballs. This seems to happen on macOS only ... the same project works fine on iOS.
Here's an example of what I mean:
List (selection: $multiSelectedContacts) {
ForEach(items) { item in
// 1. this subview is the problem ... replace it with the contents of the subview, and it works fine
PlainContentItemView(item: item)
// 2. Uncomment this part for it to work fine (and comment out PlainContentItemView above)
/*HStack {
if let timestamp = item.timestamp, let itemNumber = item.itemNumber {
Text("\(itemNumber) - \(timestamp, formatter: itemFormatter)")
}
}*/
}
}
struct PlainContentItemView: View {
let item: Item
var body: some View {
HStack {
if let timestamp = item.timestamp, let itemNumber = item.itemNumber {
Text("\(itemNumber) - \(timestamp, formatter: itemFormatter)")
}
}
}
}
Item is a NSManagedObject subclass, and conforms to Identifiable by using the objectID string value.
With this, scrolling up and down using the scrolling handle, causes stuttering scrolling and can beachball on my machine (MacBook Pro M1).
If I comment out the ‘PlainContentItemView’ and just use the HStack directly (which is what was extracted to ‘PlainContentItemView’), the performance noticeably improves, and I can scroll up and down smoothly.
Is this just a bug with SwiftUI, and/or can I do something to improve this?
So I was trying to use an NSArrayController to bind the contents of a property , first I tried using NSDictionary and it worked great, here's what I did:
@interface ViewController : NSViewController
@property IBOutlet ArrayController * tableCities;
@end
...
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSString* filePath = @"/tmp/city_test.jpeg";
NSDictionary *obj = @{@"image": [[NSImage alloc] initByReferencingFile:filePath],
@"name": @"NYC",
@"filePath": filePath};
NSDictionary *obj2 = @{@"image": [[NSImage alloc] initByReferencingFile:filePath],
@"name": @"Chicago",
@"filePath": filePath};
NSDictionary *obj3 = @{@"image": [[NSImage alloc] initByReferencingFile:filePath],
@"name": @"Little Rock",
@"filePath": filePath};
[_tableCities addObjects:@[obj, obj2, obj3]];
}
@end
Now for an NSPopUpButton, binding the Controller Key to the ArrayController and the ModelKeyPath to "name" works perfectly and the popupbutton will show the cities as I expected.
But now, instead of using an NSDictionary I wanted to use a custom class for these cities along with an NSMutableArray which holds the objects of this custom class. I'm having some trouble going about this.
We have to draw polygons inside a MKMapView based on coordinates retrieved from external source.
It seems that MapKit does not behave correctly where polygons have single-vertex self-intersection.
Here it's a simple point list example (every element is a pair of latitude and longitude values):
[(0, 0), (20, 0), (10, 10), (20, 20), (0, 20), (10, 10), (0, 0)]
The next image shows the rendering issue.
But if the list is slightly changed in this way
[(0, 0), (20, 0), (10, 10), (20, 20), (0, 20), (15, 10), (0, 0)]
the issue disappears. The next image shows it.
So it's not a self-intersection and self-tangency problem, but we think single-vertex self-intersection is a buggy edge case for MapKit.
Right now we fixed this problem by finding the duplicated coordinates and applying a small offset (1e-8) to one of them, but it's a temporary solution and adds rendering delay.
The problem is not due to iOS versions, since iOS 17 and 18 have the same issue. Also it happens on simulators and real devices.
Here is the playground example, based mostly on the "Map Playground" template Xcode offers. If you run it without modifying it, the playground shows the "bugged" polygon. If you use notBugPoints instead of the default bugPoints for the polygon, the playground shows the "not-bugged" polygon.
import MapKit
import PlaygroundSupport
// Create an MKMapViewDelegate to provide a renderer for our overlay
class MapViewDelegate: NSObject, MKMapViewDelegate {
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let overlay = overlay as? MKPolygon {
let polygonRenderer = MKPolygonRenderer(overlay: overlay)
polygonRenderer.fillColor = .red
return polygonRenderer
}
return MKOverlayRenderer(overlay: overlay)
}
}
// Create a strong reference to a delegate
let delegate = MapViewDelegate()
// Create an MKMapView
let mapView = MKMapView(frame: CGRect(x: 0, y: 0, width: 800, height: 800))
mapView.delegate = delegate
// Configure The Map elevation and emphasis style
let configuration = MKStandardMapConfiguration(elevationStyle: .realistic, emphasisStyle: .default)
mapView.preferredConfiguration = configuration
// Create an overlay
let bugPoints = [
MKMapPoint(CLLocationCoordinate2DMake(0, 0)),
MKMapPoint(CLLocationCoordinate2DMake(20, 0)),
MKMapPoint(CLLocationCoordinate2DMake(10, 10)),
MKMapPoint(CLLocationCoordinate2DMake(20, 20)),
MKMapPoint(CLLocationCoordinate2DMake(0, 20)),
MKMapPoint(CLLocationCoordinate2DMake(10, 10)),
MKMapPoint(CLLocationCoordinate2DMake(0, 0))
]
let notBugPoints = [
MKMapPoint(CLLocationCoordinate2DMake(0, 0)),
MKMapPoint(CLLocationCoordinate2DMake(20, 0)),
MKMapPoint(CLLocationCoordinate2DMake(10, 10)),
MKMapPoint(CLLocationCoordinate2DMake(20, 20)),
MKMapPoint(CLLocationCoordinate2DMake(0, 20)),
MKMapPoint(CLLocationCoordinate2DMake(15, 10)),
MKMapPoint(CLLocationCoordinate2DMake(0, 0))
]
let polygon = MKPolygon(points: bugPoints, count: notBugPoints.count)
mapView.addOverlay(polygon)
// Frame our annotation and overlay
mapView.camera = MKMapCamera(lookingAtCenter: CLLocationCoordinate2DMake(10, 10), fromDistance: 5000000, pitch: 0, heading: 0)
// Add the created mapView to our Playground Live View
PlaygroundPage.current.liveView = mapView
It seems somewhere around the update to xcode 16 and swift 6, apple may have decided to change when view are initialized. My views suddenly pre-initialize before opening the view. Is this a new feature?
I have a regular VStack or a LazyVStack, with ForEach and navigationLinks inside. Those views that the navigation link takes you to are initializing as I am scrolling in the VStack. This is absurd, there is so much overhead going on in these views to be initialized. I can think of a fix which is to implement init functions in the onAppear, and keep a property to track if view already appeared. But before that I just want to make sure this is a new feature and not some mishap on my part, and if there is a way to disable it.
Thank you.
Hi there!
I’m experiencing an issue while loading images with Kingfisher in a LazyVStack containing around 500 items. When I use low-resolution images, everything loads correctly. However, when I switch to higher quality images, only some of them load successfully, while the rest remain stuck in a loading state, and I see the following log:
nw_connection_add_timestamp_locked_on_nw_queue [C1] Hit maximum timestamp count, will start dropping events
Interestingly, if I switch from WiFi to 5G, the images start loading again, but the loading stalls once more after a few additional images.
Could you help me understand why this happens with high-quality images and how to resolve it?
Thank you!
I have an app with looks at the @Environment(\.horizontalSizeClass) variable and it's mate vertical. On the iPhone 16 sim, if I'm in portrait orientation, It says my vertical class is .regular and horizontal is .compact.
If I rotate to landscape, it says vertical is now compact, and horizontal is still compact. That seems inconsistent.
I'm trying to wrap my head around designing for size class, how am I supposed to think about this?
What I want is two areas of the screen: The main area, which shows a graphic, and a much smaller control and data area, which has a button or two and a very narrow text display, which in my current app counts from 1 to 4. The button area
These areas ought never move from where they are, but their contents ought to rotate in place to reflect the orientation. If portrait, the button area is on the bottom, if landscapeLeft, the button are is to the right, if landscapeRight, the button area is to the left.
This currently sort of works if I test for the max of height or width from a Geometry Reader, but it doesn't handle landscapeRight or portraitUpsideDown correctly.
I have a Catalyst app that supports multiple scenes / windows. It has one "main" window type and lots of other secondary windows that can be opened. I'm using the system window restoration functionality via NSQuitAlwaysKeepsWindows to restore windows with their user activity data after the app is quit and restarted.
Normally, this works great. If I open my app, change the size and position of my window, quit, and reopen it, the window size and position comes back as expected. But if I close the window using the red stoplight button and then click the app icon to bring it back, it comes back at the default position and size.
This isn't how other system apps work - if I close the system Calendar app with the stoplight button, it comes back at the same size and position. How do I get this behavior with my Catalyst app? Is there some identifier property I need to set somewhere? I don't see such a property on UISceneConfiguration.
If it matters, I'm using the configurationForConnectingSceneSession method to configure my windows when they open, instead of setting it up in the Info.plist.