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
Posts under UI Frameworks topic

Post

Replies

Boosts

Views

Activity

NSTextLists not rendered when NSTextContentStorageDelegate textContentStorage (_:, textParagraphWith:) is implemented
I have a UITextView that contains paragraphs with text bullet lists (via NSTextList). I also implement NSTextContentStorageDelegate.textContentStorage(_:, textParagraphWith:) in order to apply some custom attributes to the text without affecting the underlying attributed text. My implementation returns a new NSParagraph that modifies the foreground color of the text. I based this on the example in the WWDC 21 session "Meet Text Kit 2". UITextView stops rendering the bullets when I implement the delegate function and return a custom paragraph. Why? func textContentStorage(_ textContentStorage: NSTextContentStorage, textParagraphWith range: NSRange) -> NSTextParagraph? { guard let originalText = textContentStorage.textStorage?.attributedSubstring(from: range) else { return nil } let updatedText = NSMutableAttributedString(attributedString: originalText) updatedText.addAttribute(.foregroundColor, value: UIColor.green, range: NSRange(location: 0, length: updatedText.length)) let paragraph = NSTextParagraph(attributedString: updatedText) // Verify that the text still contains NSTextList if let paragraphStyle = paragraph.attributedString.attribute(.paragraphStyle, at: 0, effectiveRange: nil) as? NSParagraphStyle { assert(!paragraphStyle.textLists.isEmpty) } else { assertionFailure("Paragraph has lost its text lists") } return paragraph }
0
0
251
May ’25
AVQueuePlayer Error: LoudnessManager.mm:709 unable to open stream for LoudnessManager plist
Getting this error in iPhone Portrait Mode with notch. Currrently using AVQueuePlayer to play more than 30 mp3 files one by one. All constraint properties are correct but error occures only in Apple iPhone Portrait Mode with notch series. But same code works on same iPhone in Landscape mode. **But I get this error: ** LoudnessManager.mm:709 unable to open stream for LoudnessManager plist Type: Error | Timestamp: 2025-02-07 | Process: | Library: AudioToolbox | Subsystem: com.apple.coreaudio | Category: aqme | TID: 0x42754 LoudnessManager.mm:709 unable to open stream for LoudnessManager plist LoudnessManager.mm:709 unable to open stream for LoudnessManager plist Timestamp: 2025-02-07 | Library: AudioToolbox | Subsystem: com.apple.coreaudio | Category: aqme
0
2
994
Feb ’25
Regarding ARKit camera feed zoom and macro support for closer object
I am currently developing an AR experience using ARKit with SceneKit and am looking to implement functionality that enables: Zooming into the AR camera feed, ideally leveraging the ultra-wide or telephoto lenses available on supported devices. Macro-style focus capabilities, allowing users to view and interact with virtual content closely aligned with small or nearby real-world objects (within a few centimeters). My objective is to ensure that ARKit continues to render the scene accurately while enabling a zoomed-in view or macro-level focus for better detail visibility and alignment. Could you please advise on: Whether ARKit currently supports camera zoom or allows access to macro or ultra-wide cameras within an ARSession. Limitations or considerations when using multi-camera setups in conjunction with ARKit. Any guidance or references to documentation or sample code would be greatly appreciated. Best regards, Ayush
0
0
119
May ’25
Updating sort order of items in a LazyVGrid
I have a grid setup where I'm displaying multiple images which is working fine. Images are ordered by the date they're added, newest to oldest. I'm trying to set it up so that the user can change the sort order themselves but am having trouble getting the view to update. I'm setting the fetch request using oldest to newest as default when initialising the view, then when its appears updating the sort descriptor struct ProjectImagesListView: View { @Environment(\.managedObjectContext) private var viewContext var project : Project let columns = [ GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()) ] @FetchRequest var pictures: FetchedResults<Picture> var body: some View { ScrollView { LazyVGrid(columns: columns) { ForEach(pictures) { picture in NavigationLink(destination: ProjectImageDetailView(picture: picture)) { if let pictureData = picture.pictureThumbnailData, let uiImage = UIImage(data: pictureData) { Image(uiImage: uiImage) .resizable() .scaledToFit() .frame(height: 100) } else { Image("missing") .resizable() .scaledToFit() .frame(height: 100) } } } } } .navigationBarTitle("\(project.name ?? "") Images", displayMode: .inline) .onAppear() { guard let sortOrder = getSettingForPhotoOrder() else { return } guard let sortOrderValue = sortOrder.settingValue else { return } NSLog("sortOrderPhotos: \(String(describing: sortOrder.settingValue))") if sortOrderValue == "Newest" { NSLog("sortOrderPhotos: Change from default") let newSortDescriptor = NSSortDescriptor(keyPath: \Picture.dateTaken, ascending: false) pictures.nsSortDescriptors = [newSortDescriptor] } } } func getSettingForPhotoOrder() -> Setting? { let fetchRequest: NSFetchRequest<Setting> = Setting.fetchRequest() fetchRequest.predicate = NSPredicate(format: "name = %@", "photoSortOrder") fetchRequest.fetchLimit = 1 do { let results = try viewContext.fetch(fetchRequest) return results.first } catch { print("Fetching Failed") } return nil } init(project: Project) { self.project = project _pictures = FetchRequest( entity: Picture.entity(), sortDescriptors: [ NSSortDescriptor(keyPath: \Picture.dateTaken, ascending: true) ], predicate: NSPredicate(format: "project == %@", project) ) } }
Topic: UI Frameworks SubTopic: SwiftUI
0
0
87
May ’25
Crash due to likely infinitely recursive call in SwiftUI `Color.Resolved.init`
So I'm dealing with a really obtuse crash that appears to be a stack overflow in an internal SwiftUI code path creating a Color.Resolved. I haven't found anyone one else with this issue online, and I cannot get it to reproduce on my own device. Interestingly enough, it is only happening on 1 device in the field (according to XCode crash logs). Here are some lines from the crashed thread. You can see that my code is never called, and it appears to be starting in some Array equality check checking the equality of colors (which I can't think of anywhere in my app I am doing anyway). You can see from this trace here that it appears to be a recursive call through Color.Resolved and NSColor.withColorAppearance. I don't have any idea how to solve this, but it keeps happening with at least one in-the-field device across multiple app updates. So my whole app is open source on github at https://github.com/msdrigg/roam, but I don't even use NSColor explicitly anywhere except for here which doesn't match the stack trace. I also tried changing the accent color of the app with defaults write com.msdrigg.roam AppleAccentColor -integer 1 to see if that somehow caused the crash, but my app opened up totally fine (and respected the change). Besides this, the only places I think I could be using dynamic colors is I when define an AccentColor and a WidgetBackground color for my app using xcassets, and then I use these colors from SwiftUI. In most of my app I stick to the system colors (Color.gray and such). Thread 0 Crashed: 0 libsystem_pthread.dylib 0x000000018601213c ___chkstk_darwin + 60 1 CoreFoundation 0x0000000186108434 -[NSArray isEqualToArray:] + 52 (NSArray.m:454) 2 AppKit 0x000000018a21fcd4 -[NSCoreUICatalogColor resolvedCUINamedColorForAppearance:] + 164 (NSColor.m:5057) 3 AppKit 0x0000000189c32cd4 -[NSCoreUICatalogColor resolvedColor] + 48 (NSColor.m:5148) 4 AppKit 0x0000000189c31e74 -[NSDynamicNamedColor colorUsingColorSpace:] + 32 (NSColor.m:4410) 5 SwiftUICore 0x0000000221ca9fd8 CoreColorPlatformColorGetComponents + 116 (CoreColorFunctions.m:149) 6 SwiftUICore 0x0000000221faaf28 specialized Color.Resolved.init(platformColor:) + 92 (CoreColor.swift:14) 7 SwiftUICore 0x0000000221faa5b0 Color.Resolved.init(platformColor:) + 16 (<compiler-generated>:0) 8 SwiftUI 0x00000001b53b1dc4 closure #1 in NSColor.resolve(in:) + 20 (AppKitColorConversions.swift:156) 9 SwiftUI 0x00000001b53b222c partial apply for closure #1 in static NSColor.withColorAppearance(in:_:) + 32 (<compiler-generated>:0) 10 SwiftUI 0x00000001b46b1e54 closure #1 in SubmitTriggerSource.dispatchUpdate(_:) + 28 (PlatformViewCoordinator.swift:12) 11 SwiftUI 0x00000001b5484488 thunk for @escaping @callee_guaranteed () -> () + 28 (<compiler-generated>:0) 12 AppKit 0x0000000189c174a4 +[NSAppearance _performWithCurrentAppearance:usingBlock:] + 72 (NSAppearance.m:2408) 13 SwiftUI 0x00000001b53b2088 specialized static NSColor.withColorAppearance(in:_:) + 324 (AppKitColorConversions.swift:142) 14 SwiftUI 0x00000001b53b1e7c protocol witness for ColorProvider.resolve(in:) in conformance NSColor + 68 (<compiler-generated>:151) 15 SwiftUICore 0x0000000222436e6c ColorBox.resolve(in:) + 124 (Color.swift:288) 16 SwiftUICore 0x0000000222435e30 Color.resolve(in:) + 72 (Color.swift:87) 17 SwiftUI 0x00000001b53b1c88 closure #1 in NSColor.init(_:) + 196 (AppKitColorConversions.swift:124) 18 SwiftUI 0x00000001b4542714 thunk for @escaping @callee_guaranteed (@guaranteed NSAppearance) -> (@owned NSColor) + 56 (<compiler-generated>:0) 19 AppKit 0x0000000189c31e74 -[NSDynamicNamedColor colorUsingColorSpace:] + 32 (NSColor.m:4410) //// ... Repeating for 500 lines 500 SwiftUICore 0x0000000221ca9fd8 CoreColorPlatformColorGetComponents + 116 (CoreColorFunctions.m:149) 501 SwiftUICore 0x0000000221faaf28 specialized Color.Resolved.init(platformColor:) + 92 (CoreColor.swift:14) 502 SwiftUICore 0x0000000221faa5b0 Color.Resolved.init(platformColor:) + 16 (<compiler-generated>:0) 503 SwiftUI 0x00000001b53b1dc4 closure #1 in NSColor.resolve(in:) + 20 (AppKitColorConversions.swift:156) 504 SwiftUI 0x00000001b53b222c partial apply for closure #1 in static NSColor.withColorAppearance(in:_:) + 32 (<compiler-generated>:0) 505 SwiftUI 0x00000001b46b1e54 closure #1 in SubmitTriggerSource.dispatchUpdate(_:) + 28 (PlatformViewCoordinator.swift:12) 506 SwiftUI 0x00000001b5484488 thunk for @escaping @callee_guaranteed () -> () + 28 (<compiler-generated>:0) 507 AppKit 0x0000000189c174a4 +[NSAppearance _performWithCurrentAppearance:usingBlock:] + 72 (NSAppearance.m:2408) 508 SwiftUI 0x00000001b53b2088 specialized static NSColor.withColorAppearance(in:_:) + 324 (AppKitColorConversions.swift:142) 509 SwiftUI 0x00000001b53b1e7c protocol witness for ColorProvider.resolve(in:) in conformance NSColor + 68 (<compiler-generated>:151) 510 SwiftUICore 0x0000000222436e6c ColorBox.resolve(in:) + 124 (Color.swift:288) full-log.crash
0
0
118
May ’25
LazyHstack in SwiftUI not supporting varying height views
In SwiftUI I want to create a list with LazyVstack and each row item in the LazyVstack is a LazyHstack of horizontally scrollable list of images with some description with line limit of 3 and width of every item is fixed to 100 but height of every item is variable as per description text content. But in any of the rows if the first item has image description of 1 line and the remaining items in the same row has image description of 3 lines then the LazyHStack is truncating all the image descriptions in the same row to one line making all the items in that row of same height. Why LazyHStack is not supporting items of varying height ? Expected behaviour should be that height of every LazyHStack should automatically adjust as per item content height. But it seems SwiftUI is not supporting LazyHstack with items of varying height. Will SwiftUI ever support this feature?
Topic: UI Frameworks SubTopic: SwiftUI
0
0
267
Feb ’25
Best Way to Help Users Diagnose iOS App Crashes with No UI Feedback
Hi all, We're working on an iOS application and would like to improve our ability to diagnose failures - especially in scenarios where the app crashes before it can present any UI to the user. A few specific questions: In case of an exception or crash, is there a way to log the issue so the user (or our support team) can understand the cause of the failure? If the app crashes abruptly (e.g., due to a runtime exception or crash during launch), is there a recommended way to persist error information before the process terminates? Are there Apple-supported mechanisms (like crash reporting tools or APIs) we can integrate that would help us capture such issues? What’s the best practice for enabling support teams to assist users based on crash reports - especially for crashes that happen before any user interaction? Our goal is to make sure users aren't left in the dark if the app fails to start, and to allow us to deliver timely updates or support based on the cause of the crash. Thanks in advance for your guidance!
Topic: UI Frameworks SubTopic: General Tags:
0
0
104
May ’25
Playground SwiftUI on iPad wont save .png image using fileExporter.
The SwiftUI Playground code below demonstrates that a .jpeg image can be read and written to the iOS file system. While, a.png image can only be read; the writing request appears to be ignored. Can anyone please tell me how to code to save a .png image using SwiftUI to the iOS file system. Code: import SwiftUI import UniformTypeIdentifiers /* (Copied from Playground 'Help' menu popup.) UIImage Summary An object that manages image data in your app. You use image objects to represent image data of all kinds, and the UIImage class is capable of managing data for all image formats supported by the underlying platform. Image objects are immutable, so you always create them from existing image data, such as an image file on disk or programmatically created image data. An image object may contain a single image or a sequence of images for use in an animation. You can use image objects in several different ways: Assign an image to a UIImageView object to display the image in your interface. Use an image to customize system controls such as buttons, sliders, and segmented controls. Draw an image directly into a view or other graphics context. Pass an image to other APIs that might require image data. Although image objects support all platform-native image formats, it’s recommended that you use PNG or JPEG files for most images in your app. Image objects are optimized for reading and displaying both formats, and those formats offer better performance than most other image formats. Because the PNG format is lossless, it’s especially recommended for the images you use in your app’s interface. Declaration class UIImage : NSObject UIImage Class Reference */ @main struct MyApp: App { var body: some Scene { WindowGroup { ContentView() } } } struct ImageFileDoc: FileDocument { static var readableContentTypes = [UTType.jpeg, UTType.png] static var writableContentTypes = [UTType.jpeg, UTType.png] var someUIImage: UIImage = UIImage() init(initialImage: UIImage = UIImage()) { self.someUIImage = initialImage } init(configuration: ReadConfiguration) throws { guard let data = configuration.file.regularFileContents, let some = UIImage(data: data) else { throw CocoaError(.fileReadCorruptFile) } self.someUIImage = some } func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { switch configuration.contentType { case UTType.png: if let data = self.someUIImage.pngData() { return .init(regularFileWithContents: data) } case UTType.jpeg: if let data = self.someUIImage.jpegData(compressionQuality: 1.0) { return .init(regularFileWithContents: data) } default: break } throw CocoaError(.fileWriteUnknown) } } struct ContentView: View { @State private var showingExporterPNG = false @State private var showingExporterJPG = false @State private var showingImporter = false @State var message = "Hello, World!" @State var document: ImageFileDoc = ImageFileDoc() @State var documentExtension = "" var body: some View { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundColor(.accentColor) Text(message) Button("export") { if documentExtension == "png" { message += ", showingExporterPNG is true." showingExporterPNG = true } if documentExtension == "jpeg" { message += ", showingExporterJPG is true." showingExporterJPG = true } } .padding(20) .border(.white, width: 2.0) .disabled(documentExtension == "") Button("import") { showingImporter = true } .padding(20) .border(.white, width: 2.0) Image(uiImage: document.someUIImage) .resizable() .padding() .frame(width: 300, height: 300) } // exporter .png .fileExporter(isPresented: $showingExporterPNG, document: document, contentType: UTType.png) { result in switch result { case .success(let url): message += ", .\(documentExtension) Saved to \(url.lastPathComponent)" case .failure(let error): message += ", Some error saving file: " + error.localizedDescription } } // exporter .jpeg .fileExporter(isPresented: $showingExporterJPG, document: document, contentType: UTType.jpeg) { result in switch result { case .success(let url): message += ", .\(documentExtension) Saved to \(url.lastPathComponent)" case .failure(let error): message += ", Some error saving file: " + error.localizedDescription } } // importer .fileImporter(isPresented: $showingImporter, allowedContentTypes: [.png, .jpeg]) { result in switch result { case .failure(let error): message += ", Some error reading file: " + error.localizedDescription case .success(let url): let gotAccess = url.startAccessingSecurityScopedResource() if !gotAccess { message += ", Unable to Access \(url.lastPathComponent)" return } documentExtension = url.pathExtension guard let fileContents = try? Data(contentsOf: url) else { message += ",\n\nUnable to read file: \(url.lastPathComponent)\n\n" url.stopAccessingSecurityScopedResource() return } url.stopAccessingSecurityScopedResource() message += ", Read file: \(url.lastPathComponent)" message += ", path extension is '\(documentExtension)'." if let uiImage = UIImage(data: fileContents) { self.document.someUIImage = uiImage }else{ message += ", File Content is not an Image." } } } } }
0
0
372
Feb ’25
A wrinkle converting a UIKit Document-based app to SwiftUI Document Group
The app I'm converting includes two unique document types. UI-wise they have key similarities (eg contents are password protected) But serialization/model - wise. they are different documents. I have not been able to find any documentation on options for implementing this (eg use a (abstract?) base class derived from FileDocument, with two concrete sub classes? maybe just a single subclass of FileDocument that contains model details for both file types?) Stepping back from implementation options, am I crazy for attempting to use DocumentGroup to create a single app that would need to be able to open/modify/save multiple unique document types? any/all guidance much appreciated.
Topic: UI Frameworks SubTopic: SwiftUI
0
0
67
May ’25
Conflict between offerCodeRedemption and Camera
Hello, I implemented offerCodeRedemption recently on my app in my subscription/onboarding flow. When I did, it broke my camera functionality (elsewhere in the app; totally unrelated code). I was able to fix the issue when implementing the old "AppStore.presentOfferCodeRedeemSheet" code with UIKit. I'm not sure why this is happening, but it seemed like a bug to me.
Topic: UI Frameworks SubTopic: SwiftUI
0
0
149
Feb ’25
AppEntity with @Parameter Options Works in Shortcuts App but Not with Siri
I’m working with AppIntents and AppEntity to integrate my app’s data model into Shortcuts and Siri. In the example below, I define a custom FoodEntity and use it as a @Parameter in an AppIntent. I’m providing dynamic options for this parameter via an optionsProvider. In the Shortcuts app, everything works as expected: when the user runs the shortcut, they get a list of food options (from the dynamic provider) to select from. However, in Siri, the experience is different. Instead of showing the list of options, Siri asks the user to say the name of the food, and then tries to match it using EntityStringQuery. I originally assumed this might be a design decision to allow hands-free use with voice, but I found that if you use an AppEnum instead, Siri does present a tappable list of options. So now I’m wondering: why the difference? Is there a way to get the @Parameter with AppEntity + optionsProvider to show a tappable list in Siri like it does in Shortcuts or with an AppEnum? Any clarification on how EntityQuery.suggestedEntities() and DynamicOptionsProvider interact with Siri would be appreciated! struct CaloriesShortcuts: AppShortcutsProvider { static var appShortcuts: [AppShortcut] { AppShortcut( intent: AddCaloriesInteractive(), phrases: [ "Add to \(.applicationName)" ], shortTitle: "Calories", systemImageName: "fork" ) } } struct AddCaloriesInteractive: AppIntent { static var title: LocalizedStringResource = "Add to calories log" static var description = IntentDescription("Add Calories using Shortcuts.") static var openAppWhenRun: Bool = false static var parameterSummary: some ParameterSummary { Summary("Calorie Entry SUMMARY") } var displayRepresentation: DisplayRepresentation { DisplayRepresentation(stringLiteral:"Add to calorie log") } @Dependency private var persistenceManager: PersistenceManager @Parameter(title: LocalizedStringResource("Food"), optionsProvider: FoodEntityOptions()) var foodEntity: FoodEntity @MainActor func perform() async throws -> some IntentResult & ProvidesDialog { return .result(dialog: .init("Added \(foodEntity.name) to calorie log")) } } struct FoodEntity: AppEntity { static var defaultQuery = FoodEntityQuery() @Property var name: String @Property var calories: Int init(name: String, calories: Int) { self.name = name self.calories = calories } static var typeDisplayRepresentation: TypeDisplayRepresentation { TypeDisplayRepresentation(name: "Calorie Entry") } static var typeDisplayName: LocalizedStringResource = "Calorie Entry" var displayRepresentation: AppIntents.DisplayRepresentation { DisplayRepresentation(title: .init(stringLiteral: name), subtitle: "\(calories)") } var id: String { return name } } struct FoodEntityQuery: EntityQuery { func entities(for identifiers: [FoodEntity.ID]) async throws -> [FoodEntity] { var result = [FoodEntity]() for identifier in identifiers { if let entity = FoodDatabase.allEntities().first(where: { $0.id == identifier }) { result.append(entity) } } return result } func suggestedEntities() async throws -> [FoodEntity] { return FoodDatabase.allEntities() } } extension FoodEntityQuery: EntityStringQuery { func entities(matching string: String) async throws -> [FoodEntity] { return FoodDatabase.allEntities().filter({$0.name.localizedCaseInsensitiveCompare(string) == .orderedSame}) } } struct FoodEntityOptions: DynamicOptionsProvider { func results() async throws -> ItemCollection<FoodEntity> { ItemCollection { ItemSection("Section 1") { for entry in FoodDatabase.allEntities() { entry } } } } } struct FoodDatabase { // Fake data static func allEntities() -> [FoodEntity] { [ FoodEntity(name: "Orange", calories: 2), FoodEntity(name: "Banana", calories: 2) ] } }
0
1
95
May ’25
PasteButton in a confirmationDialog
Hey, Anyone knows of a possible way to present a PasteButton in a .confirmationDialog on iOS? when I try adding it, it's ignored and not displayed with the rest of the buttons struct MyView: View { @State var flag: Bool = false var body: some View { Text("Some Text") .confirmationDialog("Dialog", isPresented: $flag) { Group { Button("A") {} Button("B") {} PasteButton(payloadType: Data.self) { data in } } } } }
0
0
155
Mar ’25
App Name Display
For now, my app name length is more than 20 characters, so the iPhone app name displays without a space, and uses the range operator to ensure showing the rest in the next line. Is it possible to show space and name with 2 lines?
Topic: UI Frameworks SubTopic: General Tags:
0
0
75
May ’25
NSPredicateEditorRowTemplate subviews layout
Hello everyone, I’d appreciate if anyone can tell me if there’s a way to actually control visual layout of subviews in a predicate editor row template. I have a predicate editor with custom row template subclasses, all created in code (not in IB) and it all works fine, but the problem are template (sub)views, which are not NSPopUpButton(s). As I select different items of popup buttons, effectively changing editor’s predicate property, those buttons seem to get resized according to some strange own logic, but the general the problem is that size (width) of controls, such are text fields and date pickers, gets unpredictable and they usually shrink, even though there is still a plenty of room in the template row width. Trying to resize any control in a row template by force (setFrame: or setFrameSize:) has no effect. I can show some screenshots and videos of the behaviour, as well as code samples, if necessary, but I’d like to ask first, maybe someone already knows what I’m talking about and provide some insights and solutions. Thanks in advance.
Topic: UI Frameworks SubTopic: AppKit
0
0
88
May ’25
iOS UILabel textAlignment .justified results in wrong rect by layoutManager.boundingRect
I have a UILabel subclass showing NSAttributedString in which I need to draw a rounded rectangle background color around links: import UIKit class MyLabel: UILabel { private var linkRects = [[CGRect]]() private let layoutManager = NSLayoutManager() private let textContainer = NSTextContainer(size: .zero) private let textStorage = NSTextStorage() override func draw(_ rect: CGRect) { let path = UIBezierPath() linkRects.forEach { rects in rects.forEach { linkPieceRect in path.append(UIBezierPath(roundedRect: linkPieceRect, cornerRadius: 2)) } } UIColor.systemGreen.withAlphaComponent(0.4).setFill() path.fill() super.draw(rect) } override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder: NSCoder) { super.init(coder: coder) setup() } private func setup() { numberOfLines = 0 adjustsFontForContentSizeCategory = true isUserInteractionEnabled = true lineBreakMode = .byWordWrapping contentMode = .redraw clearsContextBeforeDrawing = true isMultipleTouchEnabled = false backgroundColor = .red.withAlphaComponent(0.1) textContainer.lineFragmentPadding = 0 textContainer.maximumNumberOfLines = numberOfLines textContainer.lineBreakMode = lineBreakMode textContainer.layoutManager = layoutManager layoutManager.textStorage = textStorage layoutManager.addTextContainer(textContainer) textStorage.addLayoutManager(layoutManager) } override func layoutSubviews() { super.layoutSubviews() calculateRects() } private func calculateRects(){ linkRects.removeAll() guard let attributedString = attributedText else { return } textStorage.setAttributedString(attributedString) let labelSize = frame.size textContainer.size = labelSize layoutManager.ensureLayout(for: textContainer) let textBoundingBox = layoutManager.usedRect(for: textContainer) print("labelSize: \(labelSize)") print("textBoundingBox: \(textBoundingBox)") var wholeLineRanges = [NSRange]() layoutManager.enumerateLineFragments(forGlyphRange: NSRange(0 ..< layoutManager.numberOfGlyphs)) { _, rect, _, range, _ in wholeLineRanges.append(range) print("Whole line: \(rect), \(range)") } attributedString.enumerateAttribute(.link, in: NSRange(location: 0, length: attributedString.length)) { value, clickableRange, _ in if value != nil { var rectsForCurrentLink = [CGRect]() wholeLineRanges.forEach { wholeLineRange in if let linkPartIntersection = wholeLineRange.intersection(clickableRange) { var rectForLinkPart = layoutManager.boundingRect(forGlyphRange: linkPartIntersection, in: textContainer) rectForLinkPart.origin.y = rectForLinkPart.origin.y + (textContainer.size.height - textBoundingBox.height) / 2 // Adjust for vertical alignment rectsForCurrentLink.append(rectForLinkPart) print("Link rect: \(rectForLinkPart), \(linkPartIntersection)") } } if !rectsForCurrentLink.isEmpty { linkRects.append(rectsForCurrentLink) } } } print("linkRects: \(linkRects)") setNeedsDisplay() } } And I use this as such: let label = MyLabel() label.setContentHuggingPriority(.required, for: .vertical) label.setContentHuggingPriority(.required, for: .horizontal) view.addSubview(label) label.snp.makeConstraints { make in make.width.lessThanOrEqualTo(view.safeAreaLayoutGuide.snp.width).priority(.required) make.horizontalEdges.greaterThanOrEqualTo(view.safeAreaLayoutGuide).priority(.required) make.center.equalTo(view.safeAreaLayoutGuide).priority(.required) } let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = .justified let s = NSMutableAttributedString(string: "Lorem Ipsum: ", attributes: [.font: UIFont.systemFont(ofSize: 17, weight: .regular), .paragraphStyle: paragraphStyle]) s.append(NSAttributedString(string: "This property controls the maximum number of lines to use in order to fit the label's text into its bounding rectangle.", attributes: [.link: URL(string: "https://news.ycombinator.com/") as Any, .foregroundColor: UIColor.link, .font: UIFont.systemFont(ofSize: 14, weight: .regular), .paragraphStyle: paragraphStyle])) label.attributedText = s Notice the paragraphStyle.alignment = .justified This results in: As you can see, the green rect background is starting a bit further to the right and also ending much further to the right. If I set the alignment to be .left or .center, then it gives me the correct rects: Also note that if I keep .justified but change the font size for the "Lorem Ipsom:" part to be a bit different, lets say 16 instead of 17, then it gives me the correct rect too: Also note that if we remove some word from the string, then also it starts giving correct rect. It seems like if the first line is too squished, then it reports wrong rects. Why is .justified text alignment giving me wrong rects? How can I fix it?
Topic: UI Frameworks SubTopic: UIKit
0
0
93
May ’25
SwiftUI @Observable Causes Extra Initializations When Using Reference Type Properties
I've encountered an issue where using @Observable in SwiftUI causes extra initializations and deinitializations when a reference type is included as a property inside a struct. Specifically, when I include a reference type (a simple class Empty {}) inside a struct (Test), DetailsViewModel is initialized and deinitialized twice instead of once. If I remove the reference type, the behavior is correct. This issue does not occur when using @StateObject instead of @Observable. Additionally, I've submitted a feedback report: FB16631081. Steps to Reproduce Run the provided SwiftUI sample code (tested on iOS 18.2 & iOS 18.3 using Xcode 16.2). Observe the console logs when navigating to DetailsView. Comment out var empty = Empty() in the Test struct. Run again and compare console logs. Change @Observable in DetailsViewModel to @StateObject and observe that the issue no longer occurs. Expected Behavior The DetailsViewModel should initialize once and deinitialize once, regardless of whether Test contains a reference type. Actual Behavior With var empty = Empty() present, DetailsViewModel initializes and deinitializes twice. However, if the reference type is removed, or when using @StateObject, the behavior is correct (one initialization, one deinitialization). Code Sample import SwiftUI enum Route { case details } @MainActor @Observable final class NavigationManager { var path = NavigationPath() } struct ContentView: View { @State private var navigationManager = NavigationManager() var body: some View { NavigationStack(path: $navigationManager.path) { HomeView() .environment(navigationManager) } } } final class Empty { } struct Test { var empty = Empty() // Comment this out to make it work } struct HomeView: View { private let test = Test() @Environment(NavigationManager.self) private var navigationManager var body: some View { Form { Button("Go To Details View") { navigationManager.path.append(Route.details) } } .navigationTitle("Home View") .navigationDestination(for: Route.self) { route in switch route { case .details: DetailsView() .environment(navigationManager) } } } } @MainActor @Observable final class DetailsViewModel { var fullScreenItem: Item? init() { print("DetailsViewModel Init") } deinit { print("DetailsViewModel Deinit") } } struct Item: Identifiable { let id = UUID() let value: Int } struct DetailsView: View { @State private var viewModel = DetailsViewModel() @Environment(NavigationManager.self) private var navigationManager var body: some View { ZStack { Color.green Button("Show Full Screen Cover") { viewModel.fullScreenItem = .init(value: 4) } } .navigationTitle("Details View") .fullScreenCover(item: $viewModel.fullScreenItem) { item in NavigationStack { FullScreenView(item: item) .navigationTitle("Full Screen Item: \(item.value)") .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { withAnimation(completionCriteria: .logicallyComplete) { viewModel.fullScreenItem = nil } completion: { var transaction = Transaction() transaction.disablesAnimations = true withTransaction(transaction) { navigationManager.path.removeLast() } } } } } } } } } struct FullScreenView: View { @Environment(\.dismiss) var dismiss let item: Item var body: some View { ZStack { Color.red Text("Full Screen View \(item.value)") .navigationTitle("Full Screen View") } } } Console Output With var empty = Empty() in Test DetailsViewModel Init DetailsViewModel Init DetailsViewModel Deinit DetailsViewModel Deinit Without var empty = Empty() in Test DetailsViewModel Init DetailsViewModel Deinit Using @StateObject Instead of @Observable DetailsViewModel Init DetailsViewModel Deinit Additional Notes This issue occurs only when using @Observable. Switching to @StateObject prevents it. This behavior suggests a possible issue with how SwiftUI handles reference-type properties inside structs when using @Observable. Using a struct-only approach (removing Empty class) avoids the issue, but that’s not always a practical solution. Questions for Discussion Is this expected behavior with @Observable? Could this be an unintended side effect of SwiftUI’s state management? Are there any recommended workarounds apart from switching to @StateObject? Would love to hear if anyone else has run into this or if Apple has provided any guidance!
0
0
341
Feb ’25
SwiftUI Tabview - how to "kill" the views we do not use
I have the MainView as the active view if the user is logged in(authenticated). the memory allocations when we run profile is pretty good. We have graphql fetching, we have token handling eg: This is All heap: 1 All Heap & Anonymous VM 13,90 MiB 65408 308557 99,10 MiB 373965 Ratio: %0.14, %0.86 After what i have checked this is pretty good for initialise and using multiple repositories eg. But when we change tabs: 1 All Heap & Anonymous VM 24,60 MiB 124651 543832 156,17 MiB 668483 Ratio: %0.07, %0.40 And that is not pretty good. So i guess we need to "kill" it or something. How? I have tried some techniques in a forum this was a recommended way: public struct LazyView<Content: View>: View { private let build: () -> Content @State private var isVisible = false public init(_ build: @escaping () -> Content) { self.build = build } public var body: some View { build() Group { if isVisible { build() } else { Color.clear } } .onAppear { isVisible = true } .onDisappear { isVisible = false } } } But this did not help at all. So under here is the one i use now. So pleace guide me for making this work. import DIKit import CoreKit import PresentationKit import DomainKit public struct MainView: View { @Injected((any MainViewModelProtocol).self) private var viewModel private var selectedTabBinding: Binding<MainTab> { Binding( get: { viewModel.selectedTab }, set: { viewModel.selectTab($0) } ) } public init() { // No additional setup needed } public var body: some View { NavigationStack(path: Binding( get: { viewModel.navigationPath }, set: { _ in } )) { TabView(selection: selectedTabBinding) { LazyView { FeedTabView() } .tabItem { Label("Feed", systemImage: "house") } .tag(MainTab.feed) LazyView { ChatTabView() } .tabItem { Label("Chat", systemImage: "message") } .tag(MainTab.chat) LazyView { JobsTabView() } .tabItem { Label("Jobs", systemImage: "briefcase") } .tag(MainTab.jobs) LazyView { ProfileTabView() } .tabItem { Label("Profile", systemImage: "person") } .tag(MainTab.profile) } .accentColor(.primary) .navigationDestination(for: MainNavigationDestination.self) { destination in switch destination { case .profile(let userId): Text("Profile for \(userId)") case .settings: Text("Settings") case .jobDetails(let id): Text("Job details for \(id)") case .chatThread(let id): Text("Chat thread \(id)") } } } } } import SwiftUI public struct LazyView<Content: View>: View { private let build: () -> Content public init(_ build: @escaping () -> Content) { self.build = build } public var body: some View { build() } }
0
0
207
Mar ’25
Removing sidebar divider in NavigationSplitView
Hi, I’m practicing with NavigationSplitView for macOS and customizing the sidebar. I’ve managed to adjust most parts, but I couldn’t remove the sidebar’s divider. It seems like it’s not possible in modern SwiftUI. My AppKit knowledge is also not very strong. How can I remove the sidebar divider? I want to use a plain background. I also solved it by creating my own sidebar, but I wanted to try it using NavigationSplitView.
Topic: UI Frameworks SubTopic: SwiftUI Tags:
0
0
212
Mar ’25