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.

All subtopics

Post

Replies

Boosts

Views

Activity

SwiftUI Gestures: Sequenced Long Press and Drag
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!
3
0
268
3w
Help Loading an External CSV File on iOS in Swift
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 &gt;= 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) -&gt; 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() } }
1
0
210
3w
FocusState and pickers error
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!
3
0
258
3w
document-based sample code doesn't work... work around?
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.
1
1
189
3w
How can a window be visible but not in the onscreen list?
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 &gt; 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.
2
0
214
3w
Image Playground API
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'?
1
0
231
3w
send data or share variables between Immersive view and "attached" 2D view
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)?
1
0
204
3w
how to show different launch screen for different language
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.
1
0
185
3w
Content size of NSWindow returns zero frame after setting view on macOS 15.0 and xCode 16.1
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?
1
0
236
3w
UIButtonLegacyVisualProvider Crash
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?
2
0
190
3w
Is there a way to opt a Catalyst app into supporting preferred text size?
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.
0
0
151
3w
Identify what is being dragged globally
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) } }
0
0
127
3w
SwiftUI 'List' performance issue on macOS
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?
0
0
194
3w
Cocoa Binding for custom class
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.
0
0
140
3w
MapKit polygon render bug when single-vertex self-intersection
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
2
0
201
3w
Pre-initialization of views?
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.
2
0
129
3w
Issue with High-Quality Images Stalling in LazyVStack with Kingfisher
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!
1
0
428
3w
SizeClasses, really?
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.
1
0
140
3w
How can I make my multi-window Catalyst app restore window size and position after closing with stoplight button?
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.
1
0
235
3w