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

Siri Shortcuts of Siri Intent to Voice Control Parts of App
I am new to the idea of Siri Shortcuts and App Intents. What I want to do is use Siri to run a function in my app. Such as saying to Siri Zoom in map and that will then call a function in my app where I can zoom in the map. Similarly, I could say Zoom out map and it would call a function to zoom out my map. I do not need to share any sort of shortcut with the Shortcuts app. Can someone please point me in the right direction for what type of intents I need to use for this?
0
0
190
Apr ’25
Custom Trait with UITraitBridgedEnvironmentKey not writing back to UITraitCollection
Hello, In my SwiftUI App i'm trying to create a custom UI trait and a matching bridged SwiftUI environment key. I want to override the environment key in a swift view and then have that reflect in the current UITraitCollection. I'm following the pattern in the linked video but am not seeing the changes reflect in the current trait collection when I update the swift env value. I can't find anything online that is helping. Does anyone know what I am missing? https://developer.apple.com/videos/play/wwdc2023/10057/ // Setup enum CustomTheme: String, Codable { case theme1 = “theme1”, theme2 = “theme2” } struct customThemeTrait: UITraitDefinition { static let defaultValue = brand.theme1 static let affectsColorAppearance = true static let identifier = "com.appName.customTheme" } extension UITraitCollection { var customTheme: CustomTheme { self[customThemeTrait.self] } } extension UIMutableTraits { var customTheme: CustomTheme { get { self[customThemeTrait.self] } set { self[customThemeTrait.self] = newValue } } } private struct customThemeKey: EnvironmentKey { static let defaultValue: CustomTheme = .theme1 } extension customThemeKey: UITraitBridgedEnvironmentKey { static func read(from traitCollection: UITraitCollection) -> CustomTheme { traitCollection.customTheme } static func write(to mutableTraits: inout UIMutableTraits, value: CustomTheme) { mutableTraits.customTheme = value } } extension EnvironmentValues { var customTheme: CustomTheme { get { self[customThemeKey.self] } set { self[customThemeKey.self] = newValue } } } // Attempted Usage extension Color { static func primaryBackground() -> Color { UITraitCollection.current.customTheme == .theme1 ? Color.red : Color.blue } } struct ContentView: View { @State private var theme = .theme1 var body: some View { if (dataHasLoaded && themeIsSet) { HomeView() .environment(\.customTheme, theme) } else { SelectThemeView( theme: self.theme, setContentThemeHandler) } } func setContentThemeHandler(theme: customTheme) { self.theme = theme } } struct HomeView() { @Environment(\.customTheme) private var currentTheme: customTheme var body: some View { VStack { Text("currentTheme: \(currentTheme.rawValue)") .background(Color.primaryBackground()) Text("currentUITrait: \(UITraitCollection.current.customTheme.rawValue)") .background(Color.primaryBackground()) } } } OUTCOME: After selecting theme2 in the theme selector view and navigating to the homeView, the background is still red and the env and trait values print the following: currentTheme: theme2 currentUITrait: theme1 Can anyone help me identify what I am missing?
1
0
95
Apr ’25
Scrolling up in List after having quickly scrolled down becomes jumpy
There seems to be a bug; when scrolling very quickly down a List, and then scrolling up at normal speed, scrolling becomes very janky and jumpy, often skipping one or two rows. This only happens on macOS. I'm kind of surprised I've seen no one else mention this bug, as I can recreate it in a very simple Xcode Project. I'm wondering if anyone knows of a workaround? Steps to reproduce: Build and launch the code below Very quickly scroll all the way down using the scrollbar Scroll up at a normal speed, after a few rows it will get janky Code: struct MinimalAlbum: Identifiable { let id: Int let title: String } struct ContentView: View { private let staticAlbums: [MinimalAlbum] = (0..<1000).map { i in MinimalAlbum(id: i, title: "Album Title \(i)") } var body: some View { List { ForEach(staticAlbums) { album in Text("Album ID: \(album.id) - \(album.title)") .frame(height: 80) // Fixed height } } } }
2
0
116
Apr ’25
MapKit causing TabBar Overwrite
Hi everyone! I am having a bit of trouble with why my Map() is overwriting my customized tabBar settings. Specifically my tab bar background. (White -> Black) Map(position: $cameraPosition) { UserAnnotation() } .toolbarBackground(.hidden, for: .tabBar) This above ^ is a view which acts as a tab view for my tab bar. I have customized my tab bar as follows just so it was obvious to see me changes. let tabAppearance = UITabBarAppearance() tabAppearance.configureWithOpaqueBackground() tabAppearance.backgroundColor = .white UITabBar.appearance().standardAppearance = tabAppearance UITabBar.appearance().scrollEdgeAppearance = tabAppearance I have tried implementing solutions which is seen with my .toolbar attempt but nothing has help. I would like the tab bar to be consistent with all of my views and from my understanding the Map is overwriting those settings.
1
0
64
Apr ’25
[iOS 18 Only] Intermittent Crash at completeTransition in Custom Navigation Animation (Firebase Crashlytics)
Hi everyone, I'm encountering an intermittent crash on iOS 18 only (not reproducible locally, reported in Firebase Crashlytics) at transitionContext.completeTransition(!transitionContext.transitionWasCancelled) within my custom UIViewControllerAnimatedTransitioning. The same code runs fine on iOS 16 and 17 (no Crashlytics report for those iOS version) Here's the crash log: Crashed: com.apple.main-thread 0 libswiftCore.dylib 0x4391f0 swift_getObjectType + 40 1 ROOM 0x490c48 ItemDetailAnimator.navigationController(_:animationControllerFor:from:to:) + 47 (ItemDetailAnimator.swift:47) 2 ROOM 0x490f3c @objc ItemDetailAnimator.navigationController(_:animationControllerFor:from:to:) + 92 (<compiler-generated>:92) 3 UIKitCore 0xa2d7a4 -[UINavigationController _customTransitionController:] + 516 4 UIKitCore 0x2e51dc -[UINavigationController _immediatelyApplyViewControllers:transition:animated:operation:] + 2620 5 UIKitCore 0x1541d4 __94-[UINavigationController _applyViewControllers:transition:animated:operation:rescheduleBlock:]_block_invoke + 100 6 UIKitCore 0x150768 -[UINavigationController _applyViewControllers:transition:animated:operation:rescheduleBlock:] + 776 7 UIKitCore 0x2e7e44 -[UINavigationController pushViewController:transition:forceImmediate:] + 544 8 UIKitCore 0x2e4230 -[UINavigationController pushViewController:animated:] + 444 9 ROOM 0x66cb04 UINavigationController.pushViewController(_:animated:completion:) + 185 (UINavigationController+Room.swift:185) 10 ROOM 0x8cef4c ItemDetailCoordinator.start(animated:completion:) + 99 (ItemDetailCoordinator.swift:99) 11 ROOM 0xc6c95c protocol witness for Coordinator.start(animated:completion:) in conformance BaseCoordinator + 24 (<compiler-generated>:24) 12 ROOM 0x8ca520 AppCoordinator.startCoordinator(_:url:reference:animated:completion:) + 729 (AppCoordinator.swift:729) 13 ROOM 0x8cb248 protocol witness for URLSupportCoordinatorOpener.startCoordinator(_:url:reference:animated:completion:) in conformance AppCoordinator + 48 (<compiler-generated>:48) 14 ROOM 0xd6166c URLSupportCoordinatorOpener<>.open(url:openingController:reference:animated:completion:) + 118 (URLSupportedCoordinator.swift:118) 15 ROOM 0xc56038 RRAppDelegate.handleURL(url:completion:) + 588 (RRAppDelegate.swift:588) 16 ROOM 0xc502d0 RRAppDelegate.applicationDidBecomeActive(_:) + 330 (RRAppDelegate.swift:330) 17 ROOM 0xc5041c @objc RRAppDelegate.applicationDidBecomeActive(_:) + 52 (<compiler-generated>:52) 18 UIKitCore 0x1fb048 -[UIApplication _stopDeactivatingForReason:] + 1368 My animateTransition code is: ```func animateTransition( using transitionContext: UIViewControllerContextTransitioning) { guard let (fromView, toView, fromVC, toVC) = filterTargets(context: transitionContext) else { transitionContext.cancelInteractiveTransition() transitionContext.completeTransition(false) return } let containerView = transitionContext.containerView toView.frame = transitionContext.finalFrame(for: toVC) guard let targetView = fromVC.animationTargetView, let fromFrame = fromVC.animationTargetFrame, let toFrame = toVC.animationTargetFrame else { containerView.insertSubview(toView, aboveSubview: fromView) toView.frame = transitionContext.finalFrame(for: toVC) transitionContext.completeTransition(true) return } let newFromFrame = fromView.convert(fromFrame, to: containerView) let tempImageView: UIImageView if let target = targetView as? UIImageView, let image = targetImage ?? target.image, image.size.height != 0, target.frame.height != 0, image.size.width / image.size.height != target.frame.width / target.frame.height { targetImage = image tempImageView = UIImageView(image: image) tempImageView.frame = newFromFrame tempImageView.contentMode = .scaleAspectFit } else { tempImageView = targetView.room.asImageView() tempImageView.frame = newFromFrame } targetView.isHidden = true let tempFromView = containerView.room.asImageView() targetView.isHidden = false let tempHideView = UIView() containerView.addSubview(tempFromView) containerView.insertSubview(toView, aboveSubview: tempFromView) tempHideView.backgroundColor = .white toView.addSubview(tempHideView) containerView.addSubview(tempImageView) //Minus with item detail view y position //Need to minus navigation bar height of item detail view var tempHideViewFrame = toFrame tempHideViewFrame.origin.y -= toView.frame.origin.y tempHideView.frame = tempHideViewFrame let duration = transitionDuration(using: transitionContext) toView.alpha = 0 UIView.animate(withDuration: duration * 0.5, delay: duration * 0.5, options: .curveLinear, animations: { toView.alpha = 1 }) let scale: CGFloat = toFrame.width / newFromFrame.width let newFrame = CGRect( x: toFrame.minX - newFromFrame.minX * scale, y: toFrame.minY - newFromFrame.minY * scale, width: tempFromView.frame.size.width * scale, height: tempFromView.frame.size.height * scale) UIView.animate(withDuration: duration, delay: 0.0, options: [.curveEaseInOut], animations: { tempFromView.frame = newFrame tempImageView.frame = toFrame }, completion: { _ in tempHideView.removeFromSuperview() tempFromView.removeFromSuperview() tempImageView.removeFromSuperview() transitionContext.completeTransition(!transitionContext.transitionWasCancelled) }) }
4
0
218
Apr ’25
iOS document based app problem running on Mac (Designed for iPad)
Please can somebody help me? I have a document-based iOS in the App Store (iNetWorth). I was able to run it on my M1 Mac Mini as a Mac (Designed for iPad) app without any issues until macOS 15. So, I created a simple test app based on a TabView to try and find out why I cannot get iNetWorth to run successfully on my Mac. The issue is that when TabViewApp.swift file looks like this: import SwiftUI @main struct TabViewApp: App { var body: some Scene { /*WindowGroup { ContentView() }*/ DocumentGroup(newDocument: TextFile()) { file in ContentView(document: file.$document) } } } TabView fails to load the ContentView() - in Xcode 16.2 running on macOS 15.3.2. On opening, the TabView app prompts the user to open a new or existing file normally but it then opens a window that is empty, apart from a Documents button and a label displaying the filename with a dropdown menu (Duplicate, Move, Rename..., Export As…). If the @Binding var document: TextFile line is removed from the ContentView() and the TabViewApp.swift file is changed to: import SwiftUI @main struct TabViewApp: App { var body: some Scene { WindowGroup { ContentView() } /*DocumentGroup(newDocument: TextFile()) { file in ContentView(document: file.$document) }*/ } } the TabView app loads and displays the ContentView() correctly. Both versions of TabView, when running in Xcode on My Mac (Designed for iPad), produce these warnings: CLIENT: Failure to determine if this machine is in the process of shutting down, err=1/Operation not permitted LSPrefs: could not find untranslocated node for &lt;FSNode 0x6000013901a0&gt; { isDir = ?, path = '/private/var/folders/3f/8788c4dj50q050_4wg9fssbr0000gp/X/518B55E1-0EC4-5D84-9202-4E44410EB249/d/Wrapper/TabView.app' }, proceeding on the assumption it is not translocated: Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" LSPrefs: could not find untranslocated node for &lt;FSNode 0x6000013901a0&gt; { isDir = ?, path = '/private/var/folders/3f/8788c4dj50q050_4wg9fssbr0000gp/X/518B55E1-0EC4-5D84-9202-4E44410EB249/d/Wrapper/TabView.app' }, proceeding on the assumption it is not translocated: Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" LSPrefs: could not find untranslocated node for &lt;FSNode 0x6000013901a0&gt; { isDir = ?, path = '/private/var/folders/3f/8788c4dj50q050_4wg9fssbr0000gp/X/518B55E1-0EC4-5D84-9202-4E44410EB249/d/Wrapper/TabView.app' }, proceeding on the assumption it is not translocated: Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" LSPrefs: could not find untranslocated node for &lt;FSNode 0x6000013901a0&gt; { isDir = ?, path = '/private/var/folders/3f/8788c4dj50q050_4wg9fssbr0000gp/X/518B55E1-0EC4-5D84-9202-4E44410EB249/d/Wrapper/TabView.app' }, proceeding on the assumption it is not translocated: Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted” However, the document-based version of TabView also displays these warnings: cannot open file at line 49450 of [1b37c146ee] os_unix.c:49450: (2) open(/private/var/db/DetachedSignatures) - No such file or directory I suspect that the lack of the DetachedSignatures file is the root cause of the ContentView() not being loaded but I cannot find out how to generate a DetachedSignatures file. Adding an empty DetachedSignatures file or directory to /private/var/db/ does not help. Has anyone else experienced this problem (and maybe found a solution)? Should I raise it as a bug via Feedback or am I missing something obvious? Thanks!
4
0
219
Mar ’25
Intents UI Extension automatically dismisses
I am working on implementing a new Intents UI Extension and have noticed that when it is triggered via the "Hey Siri" voice command, the intent dismisses after a few seconds. However, if it is launched from the Shortcuts app, the intent remains active and does not dismiss automatically. Additionally, I’ve observed that this behavior occurs on specific iOS versions, such as 17.5.1 or 17.7. On other versions, like 17.4.1 or 18.4, the intent persists as expected. Does Siri automatically close the intent based on its own logic? Could the iOS version be influencing this behavior? Given the requirement to make the intent persistent, is there any option or configuration available to achieve this?
0
0
115
Apr ’25
SwiftUI Button fade animation happens with a delay when in ScrollView
When we place a Button inside a ScrollView , the fade animation of the button is delayed, so most users won't see it I think. You can see this in the trivial example struct ContentView: View { var body: some View { ScrollView { Button { // empty } label: { Text("Fade animation test") } } } } Is there any way to opt out of this behavior? In UIKit, this was also the default behavior, but you could always change it by overriding touchesShouldCancel method. I think I can probably do that by rewriting an animation completely with some custom ButtonStyle or by rewriting a Button component completely, but it doesn't seem like a good solution to me, as I want the native look and feel (in case of button animation it is pretty easy to mimic though). And also for some components, like lists, Apple has already implemented the correct behavior by themselves somehow.
3
0
100
Apr ’25
VisionOS custom hover effect and click sound
I have created a custom hover effect per this WWDC video and many other examples on the Net: https://developer.apple.com/videos/play/wwdc2024/10152/ I can get the button to expand when looked at within a VisionOS device and it will invoke a tap event when tapped but there is no click sound like a normal SwiftUI button does in VisionOS! I can't for the life of me figure out why. Any help would be appreciated!
2
0
303
Apr ’25
What happened to readable margins?
Am in the process of migrating some UIKit based apps over to SwiftUI, but for the life of me I cannot find the SwiftUI equivalent of Readable Content Margins. I have come across some workarounds that kind of, sort of work, but do not produce the same results when compared to running the same user interface written using UIKit on several sizes of iPads in portrait and landscape orientiations. is it something Apple has not gotten around to yet, because I realize SwiftUI is a work-in-progress, or do we not care about creating consistent readable margins in our apps anymore?
2
0
336
Mar ’25
UIScrollView in UIViewControllerRepresentable - UI freezes when content touches bottom SafeArea
The SwiftUI ScrollView lacks some features I need and so I created a custom MyScrollView based on UIScrollView wrapped within a UIViewControllerRepresentable. While this works fine in general I know came across a very strange problem: When MyScrollView is used in a sheet and its content touches bottom SafeArea, the UI freezes as soon as the should be displayed. The code below shows the problem as well in preview as on the simulator and on devices. Please note that the code is tuned do the display size of an iPhone 16 Pro. When running on different devices one might need to adjust height of the Color.yellow. In the demo code the UI freezes if the Color.yellow has a height between 738 to 771 pixels. Every other height is fine. Is there something wrong with my implementation of MyScrollView? When using ScrollView instead, everything works fine. Code: struct ContentView: View { @State private var showSheet: Bool = false var body: some View { ZStack { Button("Show Sheet") { showSheet = true } } .sheet(isPresented: $showSheet) { VStack { Text("Some Header Content") MyScrollView { VStack { Color.yellow //.frame(height: 737) // works .frame(height: 738) // does NOT works // ... //.frame(height: 771) // does NOT works //.frame(height: 772) // works } } .ignoresSafeArea() } } } } struct MyScrollView<Content: View>: UIViewControllerRepresentable { let content: Content init(@ViewBuilder content: () -> Content) { self.content = content() } func makeUIViewController(context: Context) -> UIViewController { let scrollViewVC = UIViewController() scrollViewVC.view.backgroundColor = .clear let scrollView = UIScrollView() scrollView.backgroundColor = .clear let contentVC = UIHostingController(rootView: self.content) contentVC.view.backgroundColor = .clear context.coordinator.contentVC = contentVC context.coordinator.scrollView = scrollView scrollView.translatesAutoresizingMaskIntoConstraints = false scrollViewVC.view.addSubview(scrollView) NSLayoutConstraint.activate([ scrollView.topAnchor.constraint(equalTo: scrollViewVC.view.topAnchor), scrollView.bottomAnchor.constraint(equalTo: scrollViewVC.view.bottomAnchor), scrollView.leadingAnchor.constraint(equalTo: scrollViewVC.view.leadingAnchor), scrollView.trailingAnchor.constraint(equalTo: scrollViewVC.view.trailingAnchor) ]) contentVC.willMove(toParent: scrollViewVC) scrollViewVC.addChild(contentVC) contentVC.view.translatesAutoresizingMaskIntoConstraints = false scrollView.addSubview(contentVC.view) NSLayoutConstraint.activate([ contentVC.view.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor), contentVC.view.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor), contentVC.view.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor), contentVC.view.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor), contentVC.view.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor) ]) contentVC.didMove(toParent: scrollViewVC) return scrollViewVC } func updateUIViewController(_ uiViewController: UIViewController, context: Context) { context.coordinator.contentVC?.rootView = content } func makeCoordinator() -> Coordinator { return Coordinator() } class Coordinator { var contentVC: UIHostingController<Content>? var scrollView: UIScrollView? init() { //... } } } #Preview { ContentView() }
Topic: UI Frameworks SubTopic: SwiftUI
2
0
112
Apr ’25
Access DocumentGroup container by external WindowGroup
Hi, I am currently developing a document-based application for macOS and have encountered a challenge related to document container management. Specifically, I need to open a windowGroup that shares the same container as the one used in the DocumentGroup. However, my current approach of using a global shared model container has led to unintended behavior: any new document created is linked to existing ones, and changes made in one document are reflected across all documents. To address this issue, I am looking for a solution that allows each newly created document to be individualized while still sharing the document container with all relevant WindowGroups that require access to the data it holds. I would greatly appreciate any insights or recommendations you might have on how to achieve this. Thank you for your time and assistance. Best regards, Something like: @main struct Todo: App { var body: some Scene { DocumentGroup(editing: Item.self, contentType: .item) { ContentView() } WindowGroup { UndockView() .modelContainer(of documentGroup above) } } }
0
0
89
Apr ’25
Loading secondary Icon
I want to add the option to choose an alternative icon inside the app. Is there a way to load an icon asset from within the app? I downloaded Apple’s alternative icon sample, which is supposed to show a list of icons to choose from, but even in the sample, it did not work. So the current solution is to add every alternative icon along with another image asset of the same image to display to the user. This sounds like a waste of bytes. Thank you in advance for any help.
Topic: UI Frameworks SubTopic: General
0
0
80
Mar ’25
The @Environment(\.dismiss) value in SwiftUI for macOS does not dismiss a sheet presented by an NSWindowController.
I'm wondering what the correct, or recommended, way is to dismiss a SwiftUI that is being presented as a sheet hosted by an NSHostingController. The usual technique of invoking @Environment(\.dismiss) does not appear to work. Consider the code below. An NSWindowController is attempting to display a SwiftUI SettingsView as a sheet. The sheet is correctly presented, but the SettingsView is unable to dismiss itself. I am able to make it work by passing a closure into SettingsView that calls back to the NSWindowController but it's rather convoluted because SettingsView doesn't know the view controller that's hosting it until after SettingsView has been created, which means "finding" that view controller in the window controller to dismiss is more involved than it should be. Is there a better strategy to leverage here? final class MyViewController: NSViewController { @IBAction func buttonClicked(_ sender: NSButton) { if let presenter = window?.contentViewController { presenter.presentAsSheet(NSHostingController(rootView: SettingsView())) } } } struct SettingsView: View { @Environment(\.dismiss) private var dismiss var body: some View { VStack { Button("Cancel", role: .cancel) { dismiss() // This call does not dismiss the sheet. } .keyboardShortcut(.cancelAction) } } } Thank you. macOS 15.4.1 (24E263), Xcode 16.3 (16E140)
0
0
93
Apr ’25
Reliable APIs to check if a Hotkey/Shortcut is already in use?
In our application we have two usecases for a Hotkey/Shortcut identification API/method. We have some predefined shortcuts that will ship with our MacOS application. They may or may not change dynamically, based on what the user has already set as shortcuts/hotkeys, and also to avoid any important system wide shortcuts that the user may or may not have changed. We allow the user to customize the shortcuts/hotkeys in our application, so we want to show what shortcuts the user already has in use system-wide and across their OS experience. This gives rise to the need for an API that lets us know which shortcut/hotkeys are currently being used by the user and also the current system wide OS shortcuts in use. Please let me know if there are any APIs in AppKit or SwiftUI we can use for the above
0
0
308
Mar ’25
NSLayoutManager returning inconsistent values for a glyph's text container and its line fragment rect
TLDR: NSLayoutManager's textContainer(forGlyphAt:effectiveRange:) and lineFragmentRect(forGlyphRange:effectiveRange:) are returning inconsistent results. Context: I'm developing a word processing app that paginates from an NSTextStorage using NSLayoutManager. My app uses a text attribute (.columnType) to paginate sub-ranges of the text at a time, ensuring that each columnRange gets a container (or series of containers across page breaks) to fit. This is to support both multi-column and standard full-page-width content. After any user edit, I update pagination data in my Paginator model class. I calcuate frames/sizes for the views/containers, along with what superview they belong to (page). The UI updates accordingly. In order to determine whether the columnRange has overflowed from a container due to a page break OR whether the range of text hasn't overflowed its container and is actually using less space than available and should be sized down, I call both: layoutManager.textContainer(forGlyphAt: lastGlyphOfColumn, effectiveRange: &actualGlyphRangeInContainer)` // and `layoutManager.lineFragmentRect(forGlyphAt: lastGlyphOfColumn, effectiveRange: nil) Apple Documentation notes that both these calls force glyph generation and layout. As I'm in early development, I have not set non-contiguous layout. So these should be causing full layout, assuring accurate return values. Or so I'd hoped. This does work fine in many cases. I edit. Pagination works. But then I'll encounter UI-breaking inconsistent returns from these two calls. By inconsistent, I mean that the second call returns a line fragment rect that is in the container coordinates of A DIFFERENT container than the container returned by the first call. To be specific, the line fragment rect seems to be in the coordinates of the container that comes next in layoutManager.textContainers. Example Code: if !layoutManager.textContainers.indices.contains(i) { containerToUse = createTextContainer(with: availableSize) layoutManager.addTextContainer(containerToUse) } else { // We have a container already but it may be // the wrong size. containerToUse = layoutManager.textContainers[i] if containerToUse.size.width != availableSize.width { // Mandatory that we resize if we don't have // a matching width. Height resizing is not // mandatory and requires a layout check below. containerToUse.size = availableSize } } let glyphRange = layoutManager.glyphRange(forCharacterRange: remainingColumnRange, actualCharacterRange: nil) let lastGlyphOfColumn = NSMaxRange(glyphRange) - 1 var containerForLastGlyphOfColumn = layoutManager.textContainer(forGlyphAt: lastGlyphOfColumn, effectiveRange: &actualGlyphRangeInContainer) if containerForLastGlyphOfColumn != containerToUse && containerToUse.size.height < availableSize.height { // If we are here, we overflowed the container, // BUT the container we overflowed didn't use // the maximum remaining page space (this // means it was a pre-existing container that // needs to be sized up and checked once more). // NOTE RE: THE BUG: // at this point, prints show... // containerToUse.size.height // =628 // availableSize.height // =648 containerToUse.size = availableSize containerForLastGlyphOfColumn = layoutManager.textContainer(forGlyphAt: lastGlyphOfColumn, effectiveRange: &actualGlyphRangeInContainer) } // We now check again, knowing that the container we // are testing flow into is the max size it can be. if containerForLastGlyphOfColumn != containerToUse { // If we are here, we have overflowed the // container, so containerToUse size SHOULD be // final/accurate, since it is fully used. actualCharRangeInContainer = layoutManager.characterRange(forGlyphRange: actualGlyphRangeInContainer, actualGlyphRange: nil) // Start of overflow range is the first character // in the container that was overflowed into. let overflowLoc = actualCharRangeInContainer.location remainingColumnRange = NSRange(location: overflowLoc, length: remainingColumnRange.length - overflowLoc) // Update page count as we have broken to a new page currentPage += 1 } else { // If we are here, we have NOT overflowed // from the container. BUT... // THE BUG: // ***** HERE IS THE BUG! ***** lineFragmentRectForLastChar = layoutManager.lineFragmentRect(forGlyphAt: lastGlyphOfColumn, effectiveRange: nil) let usedHeight = lineFragmentRectForLastChar.maxY // BUG: ^The lines of code above return a // fragment rect that is in the coordinates // of the WRONG text container. Prints show: // usedHeight // =14 // usedHeight shouldn't be just 14 if this is // the SAME container that, when it was 628 // high, resulted in text overflowing. // Therefore, the line fragment here seems // to be in the coordinates of the ENSUING // container that we overflowed INTO, but // that shouldn't be possible, since we're in // a closure for which we know: // // containerForLastGlyphOfColumn == containerToUse // // If the last glyph container is the container // we just had to size UP, why does the final // glyph line fragment rect have a maxY of 14!? // Including ensuing code below only for context. if usedHeight < containerToUse.size.height { // Adjust container size down to usedRect containerToUse.size = CGSize(width: containerToUse.size.width, height: usedHeight) } else if usedHeight == availableSize.height { // We didn't force break to a new page BUT // we've used exactly the height of our page // to layout this column range, so need to // break to a new page for any ensuing text // columns. currentPage += 1 } else if usedHeight > containerToUse.size.height { // We should have caught this earlier. Text // has overflowed, but this should've been // caught when we checked // containerForLastGlyphOfColumn != // containerToUse. // // Note: this error has never thrown. throw PaginationError.unknownError("Oops.") } } Per my comments in the code block above, I don't understand why the very same text container that just overflowed and so had to be sized up from 628 to 648 in order to try to fit a glyph would now report that same glyph as both being IN that same container and having a line fragment rect with a maxY of just 14. A glyph couldn't fit in a container when it was 628 high, but if I size it up to 648, it only needs 14? There's something very weird going on here. Working with NSLayoutManager is a bit of a nightmare given the unclear documentation. Any help or insight here would be massively, massively appreciated.
2
0
521
Apr ’25
Hover effect in Custom UIKit Views
I am adapting my custom UI Framework for visionOS, and I'm wondering if it is going to be possible to detect hover over different UI elements within my view. The UI Framework draws to a Metal layer in a UIView. I don't currently support uihovergesturerecognizer on the view but I guess this wouldn't help, since you don't get coordinates. I can imagine an unpleasant solution might be to add invisible UIControls for each of my custom controls that are drawn in my own framework.
1
0
62
Apr ’25