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

A Summary of the WWDC25 Group Lab - UI Frameworks
At WWDC25 we launched a new type of Lab event for the developer community - Group Labs. A Group Lab is a panel Q&A designed for a large audience of developers. Group Labs are a unique opportunity for the community to submit questions directly to a panel of Apple engineers and designers. Here are the highlights from the WWDC25 Group Lab for UI Frameworks. How would you recommend developers start adopting the new design? Start by focusing on the foundational structural elements of your application, working from the "top down" or "bottom up" based on your application's hierarchy. These structural changes, like edge-to-edge content and updated navigation and controls, often require corresponding code modifications. As a first step, recompile your application with the new SDK to see what updates are automatically applied, especially if you've been using standard controls. Then, carefully analyze where the new design elements can be applied to your UI, paying particular attention to custom controls or UI that could benefit from a refresh. Address the large structural items first then focus on smaller details is recommended. Will we need to migrate our UI code to Swift and SwiftUI to adopt the new design? No, you will not need to migrate your UI code to Swift and SwiftUI to adopt the new design. The UI frameworks fully support the new design, allowing you to migrate your app with as little effort as possible, especially if you've been using standard controls. The goal is to make it easy to adopt the new design, regardless of your current UI framework, to achieve a cohesive look across the operating system. What was the reason for choosing Liquid Glass over frosted glass, as used in visionOS? The choice of Liquid Glass was driven by the desire to bring content to life. The see-through nature of Liquid Glass enhances this effect. The appearance of Liquid Glass adapts based on its size; larger glass elements look more frosted, which aligns with the design of visionOS, where everything feels larger and benefits from the frosted look. What are best practices for apps that use customized navigation bars? The new design emphasizes behavior and transitions as much as static appearance. Consider whether you truly need a custom navigation bar, or if the system-provided controls can meet your needs. Explore new APIs for subtitles and custom views in navigation bars, designed to support common use cases. If you still require a custom solution, ensure you're respecting safe areas using APIs like SwiftUI's safeAreaInset. When working with Liquid Glass, group related buttons in shared containers to maintain design consistency. Finally, mark glass containers as interactive. For branding, instead of coloring the navigation bar directly, consider incorporating branding colors into the content area behind the Liquid Glass controls. This creates a dynamic effect where the color is visible through the glass and moves with the content as the user scrolls. I want to know why new UI Framework APIs aren’t backward compatible, specifically in SwiftUI? It leads to code with lots of if-else statements. Existing APIs have been updated to work with the new design where possible, ensuring that apps using those APIs will adopt the new design and function on both older and newer operating systems. However, new APIs often depend on deep integration across the framework and graphics stack, making backward compatibility impractical. When using these new APIs, it's important to consider how they fit within the context of the latest OS. The use of if-else statements allows you to maintain compatibility with older systems while taking full advantage of the new APIs and design features on newer systems. If you are using new APIs, it likely means you are implementing something very specific to the new design language. Using conditional code allows you to intentionally create different code paths for the new design versus older operating systems. Prefer to use if #available where appropriate to intentionally adopt new design elements. Are there any Liquid Glass materials in iOS or macOS that are only available as part of dedicated components? Or are all those materials available through new UIKit and AppKit views? Yes, some variations of the Liquid Glass material are exclusively available through dedicated components like sliders, segmented controls, and tab bars. However, the "regular" and "clear" glass materials should satisfy most application requirements. If you encounter situations where these options are insufficient, please file feedback. If I were to create an app today, how should I design it to make it future proof using Liquid Glass? The best approach to future-proof your app is to utilize standard system controls and design your UI to align with the standard system look and feel. Using the framework-provided declarative API generally leads to easier adoption of future design changes, as you're expressing intent rather than specifying pixel-perfect visuals. Pay close attention to the design sessions offered this year, which cover the design motivation behind the Liquid Glass material and best practices for its use. Is it possible to implement your own sidebar on macOS without NSSplitViewController, but still provide the Liquid Glass appearance? While technically possible to create a custom sidebar that approximates the Liquid Glass appearance without using NSSplitViewController, it is not recommended. The system implementation of the sidebar involves significant unseen complexity, including interlayering with scroll edge effects and fullscreen behaviors. NSSplitViewController provides the necessary level of abstraction for the framework to handle these details correctly. Regarding the SceneDelagate and scene based life-cycle, I would like to confirm that AppDelegate is not going away. Also if the above is a correct understanding, is there any advice as to what should, and should not, be moved to the SceneDelegate? UIApplicationDelegate is not going away and still serves a purpose for application-level interactions with the system and managing scenes at a higher level. Move code related to your app's scene or UI into the UISceneDelegate. Remember that adopting scenes doesn't necessarily mean supporting multiple scenes; an app can be scene-based but still support only one scene. Refer to the tech note Migrating to the UIKit scene-based life cycle and the Make your UIKit app more flexible WWDC25 session for more information.
Topic: UI Frameworks SubTopic: General
0
0
659
Jun ’25
How do I make a UIViewRepresentable beneath SwiftUI elements ignore touches to these elements?
Hello, and an early "Merry Christmas" to all, I'm building a SwiftUI app, and one of my Views is a fullscreen UIViewRepresentable (SpriteView) beneath a SwiftUI interface. Whenever the user interacts with any SwiftUI element, the UIView registers a hit in touchesBegan(). For example, my UIView has logic for pinching (not implemented via UIGestureRecognizer), so whenever the user holds down a SwiftUI element while touching the UIView, that counts as two touches to the UIView which invokes the pinching logic. Things I've tried to block SwiftUI from passing the gesture down to the UIView: Adding opaque elements beneath control elements Adding gestures to the elements above Adding gesture masks to the gestures above Converting eligible elements to Buttons (since those seem immune) Adding SpriteViews beneath those elements to absorb gestures So far nothing has worked. As long as the UIView is beneath SwiftUI elements, any interactions with those elements will be registered as a hit. The obvious solution is to track each SwiftUI element's size and coordinates with respect to the UIView's coordinate space, then use exclusion areas, but this is both a pain and expensive, and I find it hard to believe this is the best fix for such a seemingly basic problem. I'm probably overlooking something basic, so any suggestions will be greatly appreciated
0
0
434
Dec ’24
Retrieve input field text as a keyboard extension in Swift
I am able to retrieve the text in the input field by doing: let contextBeforeInput = textDocumentProxy.documentContextBeforeInput ?? "" let contextAfterInput = textDocumentProxy.documentContextAfterInput ?? "" let fullText = contextBeforeInput + contextAfterInput However, when I'm pasting text into the input field, textDocumentProxy.documentContextBeforeInput refuses to return the entire text from the input field but instead only returns the last two sentences. I have tried this with the input fields in WhatsApp, Signal, and Telegram and it's all the same, so it doesn't seem to be caused by the specific app. At first I thought it was a limitation imposed by Apple but other third party keyboard extensions such as Grammarly are able to pick up the whole pasted text from the input field, so how are they doing it?
Topic: UI Frameworks SubTopic: UIKit
0
0
234
Jan ’25
FirstResponderView/FirstResponderIndexPath in TableView
I found when I put a webView on the screen and then remove it, several properties in TableView including firstResponderView, FirstResponderIndexPath, and FirstResponderViewType have changed. These properties are hidden and I cannot change them. firstResponderView strong holds my cell, resulting in my cell not being able to call didEndDisplayCell when it slides out of the screen as expected. What should I do to avoid firstResponderView from strong holding my cell, or what should I do to release it?
1
0
392
Dec ’24
SwiftUI Toolbar Item buttons not registering taps
Before I updated to iOS 18 everything worked fine. I pushed out an update to my application on the App Store and I had no issues. After updating to the latest OS many of my touch events are no longer working and I have no idea why. Sometimes when the app runs the touch events work fine and other times I can't click on half of my views & buttons. I am completely lost as to what might be happening. I am having issues all over the application but let's focus on the navigation stack and the toolbar item buttons. I will post some code snippets, I have been unable to replicate this in a small playground project. This is my setup, I have two buttons but lets focus on the home & notifications view. The custom Router import SwiftUI import Foundation @Observable class HomeRouter { var navPath = NavigationPath() @MainActor func navigate(to destination: HOME_ROUTES) { navPath.append(destination) } @MainActor func navigateBack() { navPath.removeLast() } @MainActor func navigateToRoot() { navPath.removeLast(navPath.count) } } Home View import os import SwiftUI import CoreLocation import NotificationCenter struct Home: View { @State public var router: HomeRouter @State private var showDetail = false @State private var showMoreFields = false @EnvironmentObject private var session: SessionStore private var log = Logger(subsystem: "com.olympsis.client", category: "home_view") init(router: HomeRouter = HomeRouter()) { self._router = State(initialValue: router) } var body: some View { NavigationStack(path: $router.navPath) { ScrollView(.vertical) { //MARK: - Welcome message WelcomeCard() .padding(.top, 25) .environmentObject(session) // MARK: - Announcements AnnouncementsView() .environmentObject(session) // MARK: - Next Events NextEvents() .environmentObject(session) // MARK: - Hot Events HotEvents() .environmentObject(session) // MARK: - Nearby Venues NearbyVenues() .environmentObject(session) Spacer(minLength: 100) } .toolbar { ToolbarItem(placement: .topBarLeading) { Text("Olympsis") .italic() .font(.largeTitle) .fontWeight(.black) } ToolbarItemGroup(placement: .topBarTrailing) { Button(action: { router.navigate(to: .messages) }) { ZStack(alignment: .topTrailing) { Image(systemName: "bubble.left.and.bubble.right") .foregroundStyle(Color.foreground) if session.invitations.count > 0 { NotificationCountView(value: $session.invitations.count) } } } Button(action: { router.navigate(to: .notifications) }) { ZStack(alignment: .topTrailing) { Image(systemName: "bell") .foregroundStyle(Color.foreground) if session.invitations.count > 0 { NotificationCountView(value: $session.invitations.count) } } } } } .background(Color("background-color/primary")) .navigationDestination(for: HOME_ROUTES.self, destination: { route in switch route { case .notifications: NotificationsView() .id(HOME_ROUTES.notifications) .environment(router) .environmentObject(session) .navigationBarBackButtonHidden() case .messages: HomeMessagesView() .id(HOME_ROUTES.messages) .environment(router) .environmentObject(session) .navigationBarBackButtonHidden() case .full_post_view(let id): AsyncPostView(postId: id) .id(HOME_ROUTES.full_post_view(id)) .environmentObject(session) .navigationBarBackButtonHidden() } }) } } } #Preview { Home() .environmentObject(SessionStore()) } The Notifications View import SwiftUI struct NotificationsView: View { @State private var notifications: [NotificationModel] = [] @Environment(HomeRouter.self) private var router @EnvironmentObject private var session: SessionStore var body: some View { ScrollView { if notifications.count > 0 { ForEach(notifications, id: \.id){ note in NotificationModelView(notification: note) } } else { VStack { Text("No new notifications") HStack { Spacer() } }.padding(.top, 50) } } .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarLeading) { Button(action:{ router.navigateBack() }) { Image(systemName: "chevron.left") .foregroundStyle(Color.foreground) } } ToolbarItem(placement: .principal) { Text("Notifications") } } .task { notifications = session.invitations.map({ i in NotificationModel(id: UUID().uuidString, type: "invitation", invite: i, body: "") }) } } } #Preview { NavigationStack { NotificationsView() .environment(HomeRouter()) .environmentObject(SessionStore()) } }
Topic: UI Frameworks SubTopic: SwiftUI
1
0
482
Dec ’24
In iOS 18, UINavigationController continuously executes pushViewController, and the lifecycle method is not executed when the viewController enters the stack
let home = homeViewController() let rootNav = UINavigationController(rootViewController: home) window?.rootViewController = rootNav window?.makeKeyAndVisible() let second = SecondViewController() home.navigationController?.pushViewController(second, animated: false) let third = ThirdViewController() home.navigationController?.pushViewController(third, animated: false) After the above is executed, the viewdidload and other related lifecycle methods in the SecondViewController are not executed。 Except for iOS18, other versions don't have this problem, so it's not a bug in iOS18, or iOS18 has optimized UINavigationController
Topic: UI Frameworks SubTopic: UIKit
2
0
584
Jan ’25
SwiftUIKit Got double back button and blank screen
I tried to update my ios from 17.2 to 18.1 on my iphone 14 pro. I use this device for testing my apps. when i go to my sdk, i got double back button and when i clicked the back button it will go to blank screen here is the ss double back button got blank screen its never happened on ios 17 and below i use coordinator and UINavigationController anyone have solutions?
1
0
288
Jan ’25
searchable issue on iOS 18
Starting with iOS 18, the behavior of searchable and searchSuggestions differs from previous versions. In iOS 17.5, searchSuggestions remained visible even after selecting an item and navigating away. However, in iOS 18, searchSuggestions are dismissed after navigation. Is there a way to keep searchSuggestions visible after navigation, as in iOS 17.5? struct ContentView: View { @State private var query = "" var body: some View { NavigationStack { Color.red .searchable(text: $query) .searchSuggestions { NavigationLink("Element") { Color.blue } } } } } iOS 18.1 iOS 17.5
2
0
441
Jan ’25
Variable modification in forEach
Hi! I'm trying to do a forEach loop on an array of objects. Here's my code : ForEach($individus) { $individu in if individu.reussite == true { individu.score -= 10 } else { individu.score = (individu.levees * 10) + 20 + individu.score } } I have an error on the code in the 'if' saying that "Type '()' cannot conform to 'View'", but I have no idea on how solving this problem.
Topic: UI Frameworks SubTopic: SwiftUI
2
0
258
Jan ’25
VStack within ScrollView on macOS 15.2 makes bottom area unclickable
Suppose there are two buttons in VStack, the second button is unclickable. I'm running macOS 15.2 with Xcode 16.2. import SwiftUI struct ContentView: View { var body: some View { ScrollView(.horizontal) { VStack { Spacer() // this button is clickable Button("foo") { print("foo") } // this button can't be clicked Button("bar") { print("bar") } } } } } If I change .horizontal -> .vertical and VStack -> HStack, the second button behave normally. If I remove ScrollView, everything works fine. it works fine before macOS 15.2.
Topic: UI Frameworks SubTopic: SwiftUI
3
0
275
Dec ’24
How to drag drop to reorder items in a horizontal scroll view?
Hi, I thought that drag drop reorder should be very easy with SwiftUI, but apparently I was wrong (unless I'm missing something). It seems to me that SwiftUI's drag-drop reorder is only easy for List, which supports .onMove modifier. However, for UI like Grid, a horizontal ScrollView with items in a HStack, I don't see any easy approach to implement this. For example, ScrollView(.horizontal) { HStack { ForEach(items) { ItemView(item) } } } Does anyone know what's the best way to implement drag drop reorder for this horizontal scroll view?
4
0
586
Jan ’25
CallKit
I am working on Agora Voice Call and using CallKit to manage incoming and outgoing calls. Issue: When I accept a call, CallKit goes behind my app. I want CallKit to remain in front of my app. Please guide me.
0
0
304
Dec ’24
NSDocument duplicate problem
The aim is to save the data of a program in 2 different formats of choice, say type1 (default) and type2. No problem when + (BOOL)autosavesInPlace is NO, you can save as… and get a choice. No problem when + (BOOL)autosavesInPlace is YES and you created a new document, you can choose when saving. But you do not get a choice when you created the new file by duplicating a existing file. It takes the type of the latter. (Using dataOfType:error:, but did not find a solution either by using writeToURL:ofType:error:, duplicateDocument:, etc.)
Topic: UI Frameworks SubTopic: AppKit
0
0
300
Jan ’25
SwiftUI Text is larger when empty
I have a simple SwiftUI Text: Text(t) .font(Font.system(size: 9)) Strangely its ideal height seems to be larger when it is empty. I initially observed this in a custom Layout container that wasn't working quite right. Eventually I looked at the height returned by v.dimensions(in:), and found that when t is non-empty the height is 11; when empty, it's 14. Subsequently I observed similar behaviour in a regular VStack container. Has anyone seen anything similar? Are there any properties that could affect this behaviour? (This is on a watch - I don't know if that matters.)
0
0
306
Jan ’25
Navigation Issue in iOS 18: Duplication of Navigation Trigger When Using @Environment(\.dismiss) in SwiftUI
I’m encountering an issue with SwiftUI navigation in iOS 18, where navigating to a DetailView causes unexpected duplication of navigation behavior when @Environment(.dismiss) is used. Code Example: Here’s a simplified version of the code: struct ContentView: View { var body: some View { NavigationStack { NavigationLink("Go to Detail View", destination: DetailView()) .padding() } } } struct DetailView: View { @Environment(\.dismiss) var dismiss var body: some View { VStack { let _ = print("DetailView") // This print statement is triggered twice in iOS 18 } } } Issue: In iOS 18, when @Environment(.dismiss) is used in DetailView, the print("DetailView") statement is triggered twice. The same code works correctly in iOS 17 and earlier, where the print statement is only triggered once, as expected. However, when I remove @Environment(.dismiss) from DetailView, the code works as intended in iOS 18, with the print statement being triggered only once and no duplication of navigation behavior. Alternative Approach with .navigationDestination(for:): I also tested using .navigationDestination(for:) to handle navigation: struct ContentView: View { var body: some View { NavigationStack { NavigationLink("Go to Detail View", destination: DetailView()) .padding() } } } struct DetailView: View { @Environment(\.dismiss) var dismiss var body: some View { VStack { let _ = print("DetailView") // This print statement is triggered twice in iOS 18 } } } Even with this alternative approach, the issue persists in iOS 18, where the print statement is triggered twice. What I've Tried: I’ve confirmed that removing @Environment(.dismiss) solves the issue, and the print statement is triggered only once, and the navigation works as expected in iOS 18 without duplication. The issue only occurs when @Environment(.dismiss) is in use, which seems to be tied to the navigation stack behavior. The code works correctly in iOS 17 and below, where the print statement is only called once. Expected Behavior: I expect the print("DetailView") statement to be called once when navigating to DetailView, and that the navigation happens only once without duplication. The presence of @Environment(.dismiss) should not cause the navigation to be triggered multiple times. Questions: Is this a known issue with iOS 18 and SwiftUI navigation? Specifically, is there a new behavior that interacts differently with @Environment(.dismiss)? Has anyone else encountered this problem, and if so, what’s the recommended way to handle it in iOS 18? Is there a workaround to ensure that the navigation doesn’t trigger more than once when using @Environment(.dismiss) in iOS 18? Any help or insights would be greatly appreciated!
1
0
377
Feb ’25
SwiftUI flash animation
I'm struggling to implement a flash animation in SwiftUI. Generally animations animate from one value to another. I'd like to animate from normal to the flashed state and then back to normal, each time the data shown by the view changes. The "flashed state" could be transparent, or a white background, or it could be a scale change for a pulse effect, or something. Example: struct MyView: View { let value: String; var body: some View { ZStack { Capsule() .fill(Color.green); Text(value); } }; }; Each time value changes, I'd like the colour of the capsule to quickly animate from green to white and back to green. I feel this should be easy - am I missing something? For bonus points: I'd like the Text to change to its new value at the midpoint of the animation, i.e. when the white text is invisible on the white background. I'd like to get the flash effect whenever I have a new value even if the new value is equal to the old value, if you see what I mean.
3
0
605
Jan ’25
QLPreviewingController can access previewed file, but cannot load files referenced by the previewed file
I have an app on the Mac App Store (so sandboxed) that includes a QuickLook Preview Extension that targets Markdown files. It established a QLPreviewingController instance for the macOS QuickLook system to access and it works. I'm in the process of updating it so that it displays inline images referenced in the file as well as styling the file's text. However, despite setting Downloads folder read-only access permission (and user-selected, though I know that shouldn't be required: no open/save dialogs here) in the extension's entitlements, Sandbox refuses too allow access to the test image: I always get a deny(1) file-read-data error in the log. FWIW, the test file is referenced in the source Markdown as an absolute unix file path. I've tried different signings and no joy. I’ve tried placing the referenced image in various other locations. Also no joy. All I can display is the error-case bundle image for 'missing image'. Question is, is this simply something that QuickLook extensions cannot do from within the sandbox, or am I missing something? Is there anything extra I can do to debug this?
0
0
405
Jan ’25
SwiftUI Canvas Text - scale to fill rectangle
How do I draw a single line of text in a SwiftUI Canvas, scaled to fill a given rectangle? Example: Canvas { context, size in let r = CGRect(origin: CGPointZero, size: size); // Whole canvas let t = Text("Hello World"); context.draw(t, in: r); } Outside of Canvas I'd add .minimumScaleFactor(0) .lineLimit(1), and I guess set a large default font size, and I'd get the result I want. But inside Canvas, .minimumScaleFactor and .lineLimit don't seem to be available; they return some View, not Text, which can't be used in context.draw. (Is there a trick to make that work?) I have written the following to do this, but I think there must be an easier way to achieve this! Suggestions? extension GraphicsContext { mutating func draw_text_in_rect(string: String, rect: CGRect) { let text = Text(string) .font(.system(size: 25)); // The font size used here does matter, because e.g. letter spacing // varies with the font size. let resolved = resolve(text); let text_size = resolved.measure(in: CGSize(width: CGFloat.infinity, height: CGFloat.infinity)); let text_aspect = text_size.width / text_size.height; let fit_size = CGSize(width: min(rect.size.width, rect.size.height*text_aspect), height: min(rect.size.height, rect.size.width/text_aspect)); let fit_rect = CGRect(x: rect.origin.x + (rect.size.width-fit_size.width)/2, y: rect.origin.y + (rect.size.height-fit_size.height)/2, width: fit_size.width, height: fit_size.height); let scale = fit_size.width / text_size.width; // For debug: // var p = Path(); // p.addRect(fit_rect); // stroke(p, with: GraphicsContext.Shading.color(.red), lineWidth: 1); translateBy(x: fit_rect.minX, y: fit_rect.minY); scaleBy(x:scale, y:scale); draw(resolved, at: CGPointZero, anchor: UnitPoint.topLeading); transform = CGAffineTransformIdentity; } };
0
0
368
Jan ’25