Construct and manage graphical, event-driven user interfaces for iOS or tvOS apps using UIKit.

Posts under UIKit tag

200 Posts

Post

Replies

Boosts

Views

Activity

How do I present a UIAlertController from the button that triggers it?
In iOS 26 there is a new way of displaying action sheets from the buttons that triggers them. I figured out that in SwiftUI you can just set the confirmationDialog view modifier on the button that triggers it. However, I can't find a way to get this behavior in UIKit when a UIButton triggers an alert. Has anyone got this work? The behavior is mentioned briefly in the "Get to know the new design system" session.
4
0
255
Jul ’25
EXC_BREAKPOINT, QuartzCore , Crash CA::Render::Image::new_image
We are seeing crashes in Xcode organizer. So far we are not able to reproduce them locally. They affect multiple app releases (some older, built with Xcode 15.x and newer built with Xcode 16.0). They only affect iOS 18.5. Is there anything that changed in latest iOS? It's hard to tell what exactly is causing this crash because setting symbolic breakpoint on CA::Render::Image::new_image(unsigned int, unsigned int, unsigned int, unsigned int, CGColorSpace*, void const*, unsigned long const*, void (*)(void const*, void*), void*) triggers this breakpoint all the time, but not necessarily with exactly the previous stack frames matching the crash report. Is it a known issue? crash.crash Thank you.
0
5
324
Jul ’25
In iOS 26, the tab bar never disappears when new controller is pushed
In my app, I have a tab bar controller whose first tab is a navigation controller. Taking a certain action in that controller will push a new controller onto the navigation stack. The new controller has hidesBottomBarWhenPushed set to true, which hides the tab bar and shows the new controller's toolbar. It's worked like this for years. But in the iOS 26 simulator (I don't have the beta installed on any physical iPhones yet), when I tried this behavior in my app, I instead saw: the tab bar remained exactly where it was when I pushed the new controller the toolbar never appeared at all and all of its buttons were inaccessible If you set the deployment target to iOS 18 and run the code in an iOS 18 simulator: when you tap "Tap Me", the new controller is pushed onto the screen simultaneously, the tab bar hides and the second controller's toolbar appears. If you set the deployment target to iOS 26 and run the code in an iOS 26 simulator: when you tap "Tap Me", the new controller is pushed onto the screen the toolbar never appears and the tab bar remains unchanged after the push animation completes Is this a bug in the iOS 26 beta, or is it an intentional behavior change in how hidesBottomBarWhenPushed works in these cases? Below is sample code that reproduces the problem: class TabController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let button = UIButton(type: .roundedRect, primaryAction: UIAction(title: "Test Action") { action in let newController = SecondaryController() self.navigationController!.pushViewController(newController, animated: true) }) button.setTitle("Tap Me", for: .normal) button.translatesAutoresizingMaskIntoConstraints = false self.view.addSubview(button) NSLayoutConstraint.activate([ self.view.centerXAnchor.constraint(equalTo: button.centerXAnchor), self.view.centerYAnchor.constraint(equalTo: button.centerYAnchor), ]) } } class SecondaryController: UIViewController { override func loadView() { super.loadView() self.toolbarItems = [ UIBarButtonItem(image: UIImage(systemName: "plus"), style: .plain, target: nil, action: nil) ] } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.navigationController?.isToolbarHidden = false } } class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? var tabController: UITabBarController? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } let tab1 = UITab(title: "Test 1", image: UIImage(systemName: "globe"), identifier: "test1") { _ in UINavigationController(rootViewController: TabController()) } let tab2 = UITab(title: "Test 2", image: UIImage(systemName: "globe"), identifier: "test2") { _ in UINavigationController(rootViewController: TabController()) } window = UIWindow(windowScene: windowScene) self.tabController = UITabBarController(tabs: [tab1, tab2]) self.window!.rootViewController = self.tabController self.window!.makeKeyAndVisible() } }
Topic: UI Frameworks SubTopic: UIKit Tags:
2
1
456
Jul ’25
`UIHostingConfiguration` for cells not cancelling touches on `UIScrollView` scroll (drag)?
The default behavior on UIScrollView is that "canCancelContentTouches" is true. If you add a UIButton to a UICollectionViewCell and then scroll (drag) the scroll view with a touch beginning on that button in that cell, it cancels ".touchUpInside" on the UIButton. However, if you have a Button in a SwiftUI view configured with "contentConfiguration" it does not cancel the touch, and the button's action is triggered if the user is still touching that cell when the scroll (drag) completes. Is there a way to forward the touch cancellations to the SwiftUI view, or is it expected that all interactivity is handled only in UIKit, and not inside of "contentConfiguration" in specific cases? This behavior seems to occur on all SwiftUI versions supporting UIHostingConfiguration so far.
4
0
89
Jun ’25
How to determine if a cold start is a background launch when using SceneDelegate (scene-based lifecycle)
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { if (application.applicationState == UIApplicationStateBackground) { // ✅ Background launch } else { // ✅ Normal foreground launch } } Previously, when using AppDelegate to handle lifecycle methods, I could determine whether a cold start was a background launch as shown above. This allowed me to accurately measure cold start time for real-world users. The platform now requires migration to scene-based lifecycle management by iOS 16. However, after migration: In both application:didFinishLaunchingWithOptions: and scene:willConnectToSession:options: methods, application.applicationState == UIApplicationStateBackground and scene.activationState == UISceneActivationStateUnattached These values remain fixed regardless of whether it's a background launch, making them unusable for differentiation. I attempted to use information from UISceneConnectionOptions for distinction but found it ineffective. Question: Are there alternative approaches to achieve this distinction?
1
0
127
Jun ’25
Ambiguous use of 'textField' in Xcode 26
func textField( _ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String ) -> Bool { if let delegate = delegate, let shouldChangeCharactersIn = delegate.textField { return shouldChangeCharactersIn(textField, range, string) } return true } This is from an extension extension TextInput: UITextFieldDelegate, ObservableTextFieldDelegateProtocol { The delegate is already a UITextFieldDelegate, but when you click on the error, it returns 7 instances of: "Found this candidate in module 'UIKit' (UIKit.UITextFieldDelegate.textField)" This doesn't give an error in Xcode 16. Is this an Xcode 26 bug?
0
0
187
Jun ’25
TN3187: Question About AppDelegate and SceneDelegate Lifecycle Method Behaviors During Migration to the UIKit Scene-Based Life Cycle
Hello, I’m currently working on migrating an existing AppDelegate-based application to use the scene-based life cycle, as recommended in the TN3187 technical note. As part of this process, I’ve been conducting some tests. After completely removing the scene-based setup (i.e., removing the UIApplicationSceneManifest from Info.plist and deleting SceneDelegate.swift), the app runs in a legacy AppDelegate-only life cycle as expected. In this case, lifecycle methods such as applicationWillEnterForeground and applicationDidEnterBackground in the AppDelegate are called properly. SceneDelegate methods are, naturally, not called—since the class is removed. However, here’s where the confusion arises: Even in this AppDelegate-only configuration, if I register for UIScene notifications via NotificationCenter, the corresponding selectors are invoked at runtime, which suggests that UIScene lifecycle notifications are still being broadcast internally by the system. Below is the test code I used, along with console output: func addObserver() { NotificationCenter.default.addObserver(self, selector: #selector(appDidEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil) if #available(iOS 13.0, *) { NotificationCenter.default.addObserver(self, selector: #selector(sceneDidEnterBackground), name: UIScene.didEnterBackgroundNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(sceneWillEnterForeground), name: UIScene.willEnterForegroundNotification, object: nil) } } @objc func sceneWillEnterForeground() { print("sceneWillForeground") } @objc func sceneDidEnterBackground() { print("sceneDidEnterBackground") } @objc func appWillEnterForeground() { print("appWillEnterForeground") } @objc func appDidEnterBackground() { print("appDidEnterBackground") } sceneWillForeground AppDelegate::willenterforeground appWillEnterForeground So, my question is: Even in an AppDelegate-only app running on iOS 13 or later, is it expected behavior that UIScene lifecycle notifications such as UIScene.didEnterBackgroundNotification and UIScene.willEnterForegroundNotification are still posted by the system? === Additional Question: The TN3187 note lists only four methods to migrate between AppDelegate and SceneDelegate. Are there any other lifecycle methods (beyond the four listed) that also require manual migration during this transition? Thank you in advance for your insights.
Topic: UI Frameworks SubTopic: UIKit Tags:
1
0
186
Jun ’25
Creating UI instance using 'XML' like data at runtime
In windows there is a support for generating Xaml strings at runtime for the UI artefact and use it on the main thread for loading the Xaml strings with properties and creating the UI artefact. Below is a code example for it. static void createxaml(hstring & str) { str = LR"( <Button xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation Name="MyButton" Content="Click Me" Width="200" Height="60" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="18" FontFamily="Segoe UI" Foreground="White" )"; } { hstring xaml; createxaml(xaml); Button obj = XamlReader::Load(xaml).as<Button>(); } My question is, Is there similar support available in uikit to create ui instances like UIButton. Is there some native support from apple that allows us to create a button object using an XML like string?
Topic: UI Frameworks SubTopic: General Tags:
2
0
93
Jun ’25
Apple recommended Approach for Implementing @Mention System with Dropdown and Smart Backspace in UITextView
I'm working on an iOS app that requires an @mention system in a UITextView, similar to those in apps like Twitter or Slack. Specifically, I need to: Detect @ Symbol and Show Dropdown: When the user types "@", display a dropdown (UITableView or similar) below the cursor with a list of mentionable users, filtered as the user types. Handle Selection: Insert the selected username as a styled mention (e.g., blue text). Smart Backspace Behavior: Ensure backspace deletes an entire mention as a single unit when the cursor is at its end, and cancels the mention process if "@" is deleted. I've implemented a solution using UITextViewDelegate textViewDidChange(_:) to detect "@", a UITableView for the dropdown, and NSAttributedString for styling mentions. For smart backspace, I track mention ranges and handle deletions accordingly. However, I’d like to know: What is Apple’s recommended approach for implementing this behavior? Are there any UIKit APIs that simplify this, for proving this experience like smart backspace or custom text interactions? I’m using Swift/UIKit. Any insights, sample code, or WWDC sessions you’d recommend would be greatly appreciated! Edit: I am adding the ViewController file to demonstrate the approach that I m using. import UIKit // MARK: - Dummy user model struct MentionUser { let id: String let username: String } class ViewController: UIViewController, UITextViewDelegate, UITableViewDelegate, UITableViewDataSource { // MARK: - UI Elements private let textView = UITextView() private let mentionTableView = UITableView() // MARK: - Data private var allUsers: [MentionUser] = [...] private var filteredUsers: [MentionUser] = [] private var currentMentionRange: NSRange? // MARK: - View Lifecycle override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white setupTextView() // to setup the UI setupDropdown() // to setup the UI } // MARK: - UITextViewDelegate func textViewDidChange(_ textView: UITextView) { let cursorPosition = textView.selectedRange.location let text = (textView.text as NSString).substring(to: cursorPosition) if let atRange = text.range(of: "@[a-zA-Z0-9_]*$", options: .regularExpression) { let nsRange = NSRange(atRange, in: text) let query = (text as NSString).substring(with: nsRange).dropFirst() currentMentionRange = nsRange filteredUsers = allUsers.filter { $0.username.lowercased().hasPrefix(query.lowercased()) } mentionTableView.reloadData() showMentionDropdown() } else { hideMentionDropdown() currentMentionRange = nil } } func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { if text.isEmpty, let attributedText = textView.attributedText { if range.location == 0 { return true } let attr = attributedText.attributes(at: range.location - 1, effectiveRange: nil) if let _ = attr[.mentionUserId] { let fullRange = (attributedText.string as NSString).rangeOfMentionAt(location: range.location - 1) let mutable = NSMutableAttributedString(attributedString: attributedText) mutable.deleteCharacters(in: fullRange) textView.attributedText = mutable textView.selectedRange = NSRange(location: fullRange.location, length: 0) textView.typingAttributes = [ .font: textView.font ?? UIFont.systemFont(ofSize: 16), .foregroundColor: UIColor.label ] return false } } return true } // MARK: - Dropdown Visibility private func showMentionDropdown() { guard let selectedTextRange = textView.selectedTextRange else { return } mentionTableView.isHidden = false } private func hideMentionDropdown() { mentionTableView.isHidden = true } // MARK: - UITableViewDataSource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return filteredUsers.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) cell.textLabel?.text = "@\(filteredUsers[indexPath.row].username)" return cell } // MARK: - UITableViewDelegate func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { insertMention(filteredUsers[indexPath.row]) } // MARK: - Mention Insertion private func insertMention(_ user: MentionUser) { guard let range = currentMentionRange else { return } let mentionText = "\(user.username)" let mentionAttributes: [NSAttributedString.Key: Any] = [ .foregroundColor: UIColor.systemBlue, .mentionUserId: user.id ] let mentionAttrString = NSAttributedString(string: mentionText, attributes: mentionAttributes) let mutable = NSMutableAttributedString(attributedString: textView.attributedText) mutable.replaceCharacters(in: range, with: mentionAttrString) let spaceAttr = NSAttributedString(string: " ", attributes: textView.typingAttributes) mutable.insert(spaceAttr, at: range.location + mentionText.count) textView.attributedText = mutable textView.selectedRange = NSRange(location: range.location + mentionText.count + 1, length: 0) textView.typingAttributes = [ .font: textView.font ?? UIFont.systemFont(ofSize: 16), .foregroundColor: UIColor.label ] hideMentionDropdown() } } // MARK: - Custom Attributed Key extension NSAttributedString.Key { static let mentionUserId = NSAttributedString.Key("mentionUserId") }
Topic: UI Frameworks SubTopic: UIKit Tags:
1
0
115
Jun ’25
iPadOS 26 + UIToolbar in Storyboard + UIDesignRequiresCompatibility
Hi Community, I found an issue and wanted to ask you for confirmation... or help? (I will, if the issue persists or gets confirmed, of course file a bug report.) Context/Setup: macOS 15.5 Xcode 26 Beta 2 iPad Simulator, seems to be any, tested with "Simulator iPad Pro 11-inch (M4)" and "Simulator iPad mini (A17 Pro)" and also two physical iPads (mini and Pro) running iPadOS 26 Beta 2. Issue: In our project we are facing a runtime issue. Condensed down, when there is a storyboard with a UIToolbar (empty or with buttons) AND the project has the new UIDesignRequiresCompatibility set to true AND we run the app on an iPad (physical device or simulator)... As soon as the storyboard is loaded and about to be displayed the app crashes, console print: "UIKitCore/UICoreHostingView.swift:54: Fatal error: init(coder:) has not been implemented" Any iPhone (physical or simulator) works fine. Also with UIDesignRequiresCompatibility set to false it works everywhere including iPads. Minimum Deployment Target has between iOS 15 to 26 does has no effect on the outcome. So it seems there is an issue with UIToolbar in Storyboards with UIDesignRequiresCompatibility on iPads. Did anyone experience the same issue or can confirm it? Any idea how to solve it? Thanks a lot!
Topic: UI Frameworks SubTopic: UIKit Tags:
1
2
421
Jun ’25
Conflict UI Display in system tabbar Liquid Glass Effect and custom tabbar with iOS 26 when using Xcode 26 build app
When using UITabBarController and set a custom tabbar: TabBarViewController.swift import UIKit class BaseViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } } class HomeViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .red navigationItem.title = "Home" tabBarItem = UITabBarItem(title: "Home", image: UIImage(systemName: "house"), tag: 0) } } class PhoneViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .purple navigationItem.title = "Phone" tabBarItem = UITabBarItem(title: "Phone", image: UIImage(systemName: "phone"), tag: 1) } } class PhotoViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .yellow navigationItem.title = "Photo" tabBarItem = UITabBarItem(title: "Photo", image: UIImage(systemName: "photo"), tag: 1) } } class SettingViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .green navigationItem.title = "Setting" tabBarItem = UITabBarItem(title: "Setting", image: UIImage(systemName: "gear"), tag: 1) } } class TabBarViewController: UITabBarController { override func viewDidLoad() { super.viewDidLoad() let homeVC = HomeViewController() let homeNav = NavigationController(rootViewController: homeVC) let phoneVC = PhoneViewController() let phoneNav = NavigationController(rootViewController: phoneVC) let photoVC = PhotoViewController() let photoNav = NavigationController(rootViewController: photoVC) let settingVC = SettingViewController() let settingNav = NavigationController(rootViewController: settingVC) viewControllers = [homeNav] let dataSource = [ CustomTabBar.TabBarModel(title: "Home", icon: UIImage(systemName: "house")), CustomTabBar.TabBarModel(title: "Phone", icon: UIImage(systemName: "phone")), CustomTabBar.TabBarModel(title: "Photo", icon: UIImage(systemName: "photo")), CustomTabBar.TabBarModel(title: "Setting", icon: UIImage(systemName: "gear")) ] let customTabBar = CustomTabBar(with: dataSource) setValue(customTabBar, forKey: "tabBar") } } CustomTabBar.swift: import UIKit class CustomTabBar: UITabBar { class TabBarModel { let title: String let icon: UIImage? init(title: String, icon: UIImage?) { self.title = title self.icon = icon } } class TabBarItemView: UIView { lazy var titleLabel: UILabel = { let titleLabel = UILabel() titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.font = .systemFont(ofSize: 14) titleLabel.textColor = .black titleLabel.textAlignment = .center return titleLabel }() lazy var iconView: UIImageView = { let iconView = UIImageView() iconView.translatesAutoresizingMaskIntoConstraints = false iconView.contentMode = .center return iconView }() private var model: TabBarModel init(model: TabBarModel) { self.model = model super.init(frame: .zero) setupSubViews() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupSubViews() { addSubview(iconView) iconView.topAnchor.constraint(equalTo: topAnchor).isActive = true iconView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true iconView.widthAnchor.constraint(equalToConstant: 34).isActive = true iconView.heightAnchor.constraint(equalToConstant: 34).isActive = true iconView.image = model.icon addSubview(titleLabel) titleLabel.topAnchor.constraint(equalTo: iconView.bottomAnchor).isActive = true titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true titleLabel.heightAnchor.constraint(equalToConstant: 16).isActive = true titleLabel.text = model.title } } private var dataSource: [TabBarModel] init(with dataSource: [TabBarModel]) { self.dataSource = dataSource super.init(frame: .zero) setupTabBars() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func sizeThatFits(_ size: CGSize) -> CGSize { var sizeThatFits = super.sizeThatFits(size) let safeAreaBottomHeight: CGFloat = safeAreaInsets.bottom sizeThatFits.height = 52 + safeAreaBottomHeight return sizeThatFits } private func setupTabBars() { backgroundColor = .orange let multiplier = 1.0 / Double(dataSource.count) var lastItemView: TabBarItemView? for model in dataSource { let tabBarItemView = TabBarItemView(model: model) addSubview(tabBarItemView) tabBarItemView.translatesAutoresizingMaskIntoConstraints = false tabBarItemView.topAnchor.constraint(equalTo: topAnchor).isActive = true tabBarItemView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true if let lastItemView = lastItemView { tabBarItemView.leadingAnchor.constraint(equalTo: lastItemView.trailingAnchor).isActive = true } else { tabBarItemView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true } tabBarItemView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: multiplier).isActive = true lastItemView = tabBarItemView } } } UIKit show both custom tabbar and system tabbar: the Xcode version is: Version 26.0 beta 2 (17A5241o) and the iOS version is: iOS 26 (23A5276f)
0
0
191
Jun ’25
Possible contradiction between ARKit's definition of UIDeviceOrientation.landscapeRight and the actual definition of UIDeviceOrientation.landscapeRight
So it seems to be that there is a contradiction between how ARKit defines UIDeviceOrientation.landscapeRight, and the actual definition of UIDeviceOrientation.landscapeRight in the UIKit documentation. In the ARKit documentation for ARCamera.transform, it says the following: This transform creates a local coordinate space for the camera that is constant with respect to device orientation. In camera space, the x-axis points to the right when the device is in UIDeviceOrientation.landscapeRight orientation—that is, the x-axis always points along the long axis of the device, from the front-facing camera toward the Home button. The y-axis points upward (with respect to UIDeviceOrientation.landscapeRight orientation), and the z-axis points away from the device on the screen side. Going through the same link, we see the definition of UIDeviceOrientation.landscapeRight given as: The device is in landscape mode, with the device held upright and the front-facing camera on the right side. There seems to be a conflict in the two definitions, that has already been asked and visualized in this StackOverflow thread The resolution of that answer says that ARKit landscapeRight, unlike what is given in UIDeviceOrientation.landscapeRight, has home button on the right, as stated in the ARCamera.transform documentation. It says that more details are given in this StackOverflow thread, but this thread talks about the discrepancy between the definitions of landscapeRight in UIDeviceOrientation and UIInterfaceOrientation, and not anything related to ARKit. So I am wondering, why does ARKit definition of landscapeRight contradict with that of UIDeviceOrientation despite explicitly mentioning it? Is it just a mistake by Apple developers that hasn't been resolved even after so long?
1
0
92
Jun ’25
Is configuration-style API (like UIButton.configuration) available for other UIKit or AppKit components?
In UIKit, UIButton provides a configuration property which allows us to create and customize a UIButton.Configuration instance independently (on a background thread or elsewhere) and later assign it to a UIButton instance. This separation of configuration and assignment is very useful for clean architecture and performance optimization. Questions: Is this configuration-style pattern (creating a configuration object separately and assigning it later) available or planned for other UIKit components such as UILabel, UITextField, UISlider, etc.? Similarly, in AppKit on macOS, are there any components (e.g. NSButton, NSTextField) that support a comparable configuration object mechanism that can be used the same way — constructed separately and assigned to the view later? This would help in building consistent configuration-driven UI frameworks across Apple platforms. Any insight or official guidance would be appreciated.
0
0
91
Jun ’25
Is it possible to auto-expand the iOS 26 text selection menu?
I've got a UIKit app that displays a lot of text, and we've completely turned off the system text selection menu and we show our own custom thing instead, to increase discoverability of our text selection actions. But now that iOS 26 can show the full menu even on iPhone, we're looking at switching back to the system menu. It still shows a smaller horizontal-layout menu at first, and then you tap the > symbol to expand to the full menu. Is it possible to jump straight to the full menu, and skip the smaller horizontal one entirely?
Topic: UI Frameworks SubTopic: UIKit Tags:
0
0
61
Jun ’25
Displaying limited contacts list in UIKit
I have an app that was written in UIKit. It's too large, and it would be much too time consuming at this point to convert it to SwiftUI. I want to incorporate the new limited contacts into this app. The way it's currently written everything works fine except for showing the limited contacts in the contact picker. I have downloaded and gone though the Apple tutorial app but I'm having trouble thinking it through into UIKit. After a couple of hours I decided I need help. I understand I need to pull the contact IDs of the contacts that are in the limited contacts list. Not sure how to do that or how to get it to display in the picker. Any help would be greatly appreciated. func requestAccess(completionHandler: @escaping (_ accessGranted: Bool) -> Void) { switch CNContactStore.authorizationStatus(for: .contacts) { case .authorized: completionHandler(true) case .denied: showSettingsAlert(completionHandler) case .restricted, .notDetermined: CNContactStore().requestAccess(for: .contacts) { granted, error in if granted { completionHandler(true) } else { DispatchQueue.main.async { [weak self] in self?.showSettingsAlert(completionHandler) } } } // iOS 18 only case .limited: completionHandler(true) @unknown default: break } } // A text field that displays the name of the chosen contact @IBAction func contact_Fld_Tapped(_ sender: TextField_Designable) { sender.resignFirstResponder() // The contact ID that is saved to the Db getTheCurrentContactID() let theAlert = UIAlertController(title: K.Titles.chooseAContact, message: nil, preferredStyle: .actionSheet) // Create a new contact let addContact = UIAlertAction(title: K.Titles.newContact, style: .default) { [weak self] _ in self?.requestAccess { _ in let openContact = CNContact() let vc = CNContactViewController(forNewContact: openContact) vc.delegate = self // this delegate CNContactViewControllerDelegate DispatchQueue.main.async { self?.present(UINavigationController(rootViewController: vc), animated: true) } } } let getContact = UIAlertAction(title: K.Titles.fromContacts, style: .default) { [weak self] _ in self?.requestAccess { _ in self?.contactPicker.delegate = self DispatchQueue.main.async { self?.present(self!.contactPicker, animated: true) } } } let editBtn = UIAlertAction(title: K.Titles.editContact, style: .default) { [weak self] _ in self?.requestAccess { _ in let store = CNContactStore() var vc = CNContactViewController() do { let descriptor = CNContactViewController.descriptorForRequiredKeys() let editContact = try store.unifiedContact(withIdentifier: self!.oldContactID, keysToFetch: [descriptor]) vc = CNContactViewController(for: editContact) } catch { print("Getting contact to edit failed: \(self!.VC_String) \(error)") } vc.delegate = self // delegate for CNContactViewControllerDelegate self?.navigationController?.isNavigationBarHidden = false self?.navigationController?.navigationItem.hidesBackButton = false self?.navigationController?.pushViewController(vc, animated: true) } } let cancel = UIAlertAction(title: K.Titles.cancel, style: .cancel) { _ in } if oldContactID.isEmpty { editBtn.isEnabled = false } theAlert.addAction(getContact) // Select from contacts theAlert.addAction(addContact) // Create new contact theAlert.addAction(editBtn) // Edit this contact theAlert.addAction(cancel) let popOver = theAlert.popoverPresentationController popOver?.sourceView = sender popOver?.sourceRect = sender.bounds popOver?.permittedArrowDirections = .any present(theAlert,animated: true) } func requestAccess(completionHandler: @escaping (_ accessGranted: Bool) -> Void) { switch CNContactStore.authorizationStatus(for: .contacts) { case .authorized: completionHandler(true) case .denied: showSettingsAlert(completionHandler) case .restricted, .notDetermined: CNContactStore().requestAccess(for: .contacts) { granted, error in if granted { completionHandler(true) } else { DispatchQueue.main.async { [weak self] in self?.showSettingsAlert(completionHandler) } } } // iOS 18 only case .limited: completionHandler(true) @unknown default: break } } // MARK: - Contact Picker Delegate extension AddEdit_Quote_VC: CNContactPickerDelegate { func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) { selectedContactID = contact.identifier let company: String = contact.organizationName let companyText = company == "" ? K.Titles.noCompanyName : contact.organizationName contactNameFld_Outlet.text = CNContactFormatter.string(from: contact, style: .fullName)! companyFld_Outlet.text = companyText save_Array[0] = K.AppFacing.true_App setSaveBtn_AEQuote() } } extension AddEdit_Quote_VC: CNContactViewControllerDelegate { func contactViewController(_ viewController: CNContactViewController, shouldPerformDefaultActionFor property: CNContactProperty) -> Bool { return false } func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) { selectedContactID = contact?.identifier ?? "" if selectedContactID != "" { let company: String = contact?.organizationName ?? "" let companyText = company == "" ? K.Titles.noCompanyName : contact!.organizationName contactNameFld_Outlet.text = CNContactFormatter.string(from: contact!, style: .fullName) companyFld_Outlet.text = companyText getTheCurrentContactID() if selectedContactID != oldContactID { save_Array[0] = K.AppFacing.true_App setSaveBtn_AEQuote() } } dismiss(animated: true, completion: nil) } }
2
0
794
Jun ’25
`UIGraphicsImageRenderer` + `drawHierarchy` gives very flat colors
My setup: a UILabel with text in it and then let aBugRenderer = UIGraphicsImageRenderer(size: aBugLabel.bounds.size) let aBugImage = aBugRenderer.image { context in aBugLabel.drawHierarchy(in: aBugLabel.bounds, afterScreenUpdates: true) } The layout and everything is correct, the image is correct, but I used my colors in the displayP3 color space to configure the source UILabel.textColor And unfortunately, the resulted image ends up being sRGB IEC61966-2.1 color space and the color appears way bleaker than when it's drawn natively. Question: how can I set up the renderer so that it draws the same color.
0
0
95
Jun ’25
Regarding the deadline for adopting the UIScene life cycle
https://developer.apple.com/forums/thread/788293 In the above thread, I received the following response: "When building with the SDK from the next major release after iOS 26, iPadOS 26, macOS 26 and visionOS 26, UIKit will assert that all apps have adopted UIScene life cycle. Apps that fail this assert will crash on launch." does this mean that there will be no app crashes caused by UIKit in iOS 26, but there is a possibility of app crashes when building with the SDK provided from iOS 27 onwards?
Topic: UI Frameworks SubTopic: UIKit Tags:
2
0
268
Jun ’25
` UIBezierPath(roundedRect:cornerRadius:)` renders Inconsistently at Specific Size-to-Radius Ratios
Hello everyone, I've encountered a fascinating and perplexing rendering anomaly when using UIBezierPath(roundedRect:cornerRadius:) to create a CGPath. Summary of the Issue: When the shortest side of the rectangle (min(width, height)) is just under a certain multiple of the cornerRadius (empirically, around 3x), the algorithm for generating the path seems to change entirely. This results in a path with visually different (and larger) corners than when the side is slightly longer, even with the same cornerRadius parameter. How to Reproduce: The issue is most clearly observed with a fixed cornerRadius while slightly adjusting the rectangle's height or width across a specific threshold. Create a UIView (contentView) and another UIView (shadowView) behind it. Set the shadowView.layer.shadowPath using UIBezierPath(roundedRect: contentView.bounds, cornerRadius: 16).cgPath. Adjust the height of the contentView. Observe the shadowPath at height 48 vs. height 49 Minimal Reproducible Example: Here is a simple UIViewController to demonstrate the issue. You can drop this into a project. Tapping the "Toggle Height" button will switch between the two states and print the resulting CGPath to the console. import UIKit class PathTestViewController: UIViewController { private let contentView = UIView() private let shadowView = UIView() private var heightConstraint: NSLayoutConstraint! private let cornerRadius: CGFloat = 16.0 private let normalHeight: CGFloat = 49 private let anomalyHeight: CGFloat = 48 override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemGray5 setupViews() setupButton() } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() updateShadowPath() } private func updateShadowPath() { let newPath = UIBezierPath(roundedRect: contentView.bounds, cornerRadius: cornerRadius).cgPath shadowView.layer.shadowPath = newPath } private func setupViews() { // ContentView (the visible rect) contentView.backgroundColor = .systemBlue contentView.translatesAutoresizingMaskIntoConstraints = false contentView.isHidden = true // ShadowView (to render the path) shadowView.layer.shadowColor = UIColor.black.cgColor shadowView.layer.shadowOpacity = 1 shadowView.layer.shadowRadius = 2 shadowView.layer.shadowOffset = .zero shadowView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(shadowView) view.addSubview(contentView) heightConstraint = contentView.heightAnchor.constraint(equalToConstant: normalHeight) NSLayoutConstraint.activate([ contentView.centerXAnchor.constraint(equalTo: view.centerXAnchor), contentView.centerYAnchor.constraint(equalTo: view.centerYAnchor), contentView.widthAnchor.constraint(equalToConstant: 300), heightConstraint, shadowView.topAnchor.constraint(equalTo: contentView.topAnchor), shadowView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), shadowView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), shadowView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), ]) } private func setupButton() { let button = UIButton(type: .system, primaryAction: UIAction(title: "Toggle Height", handler: { [unowned self] _ in let newHeight = self.heightConstraint.constant == self.normalHeight ? self.anomalyHeight : self.normalHeight self.heightConstraint.constant = newHeight UIView.animate(withDuration: 0.3) { self.view.layoutIfNeeded() } })) button.translatesAutoresizingMaskIntoConstraints = false view.addSubview(button) NSLayoutConstraint.activate([ button.centerXAnchor.constraint(equalTo: view.centerXAnchor), button.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20) ]) } } Evidence: CGPath Analysis Note: The CGPath data below is from my initial observation. At that time, height 48.7 produced a path with straight edges. Now, this "correct" path is only produced at height 49.0 or greater. The inconsistency now occurs at 48.7.* The key difference lies in the raw CGPath data. Path for Height = 48.7 (Expected Behavior) The path is constructed with lineto commands for the straight edges between the curved corners. // Path for Height 48.7 Path 0x60000300a0a0: moveto (24.4586, 0) lineto (24.5414, 0) // <-- Straight line on top edge curveto (31.5841, 0) (35.1055, 0) (38.8961, 1.19858) ... Path for Height = 48.6 (Anomalous Behavior) The lineto commands for the short edges disappear. The path is composed of continuous curveto commands, as if the two corners have merged into a single, larger curve. This creates the visual discrepancy. // Path for Height 48.6 Path 0x600003028630: moveto (24.1667, 0) lineto (24.1667, 0) // <-- Zero-length line curveto (24.1667, 0) (24.1667, 0) (24.1667, 0) lineto (25.375, 1.44329e-15) curveto (34.8362, -2.77556e-16) (43.2871, 5.9174) (46.523, 14.808) // <-- First curve curveto (48.3333, 20.5334) (48.3333, 25.8521) (48.3333, 36.4896) // <-- Second curve, no straight line in between ... min.length == 48 min.length == 49 My Questions: Is this change in the path-generation algorithm at this specific size/radius threshold an intended behavior, or is it a bug? Is this behavior documented anywhere? The threshold doesn't seem to be a clean side/radius == 2.0, so it's hard to predict. Is there a recommended workaround to ensure consistent corner rendering across these small size thresholds? Any insight would be greatly appreciated. Thank you! Environment: Xcode: 16.4 iOS: 16.5.1(iPad), 18.4(iphone simulator)
2
0
170
Jun ’25
iPadOS 26: App Window Controls Overlap Navigation Bar Buttons - Seeking Opt-Out Option (iPad 11th Gen, Objective-C)
Hello Apple Developer Community, I'm developing an application for iPadOS 26 on an 11th generation iPad, using Objective-C. With the recent update to iPadOS 26, I've noticed a significant change in how app windows are presented. Specifically, the new minimize and close buttons, similar to those found on macOS, now appear in the top-left corner of app windows. The issue I'm encountering is that these newly introduced system buttons overlap with custom buttons I've programmatically added to the left side of my app's navigation bar. This overlap affects nearly all screens in my application, making some of my essential UI elements inaccessible or difficult to interact with. I'm looking for guidance on whether there's an official way to opt out of displaying these minimize and close buttons, or perhaps a method to adjust their position or visibility to prevent them from interfering with existing UI elements. My aim is to maintain the functionality and user experience of my application without having to redesign a substantial portion of its interface. Any insights or suggestions from the community would be greatly appreciated. Thank you in advance for your help!
3
0
369
Jun ’25