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

Posts under UIKit tag

88 Posts

Post

Replies

Boosts

Views

Activity

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
149
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
486
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
278
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
158
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
137
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
188
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
1k
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
216
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
342
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
569
Jun ’25
Xcode 26 beta: 'Building the menu bar using a storyboard is no longer supported for iOS and Mac Catalyst apps. Please migrate to the UIMenuBuilder or Commands APIs.'
Support for menus in Storyboards is yanked without ever being deprecated (to my knowledge)? Really? WTF? This is a major step backwards, Apple. So nice to have to spend a month rewriting my app after WWDC each year. Re-creating a complex menu hierarchy in code is exactly what I wanted to do. Ugh.
4
1
358
Jun ’25
iOS Battery Percentage Granularity Issue
When developing an iOS app that monitors or transmits the battery percentage (using UIDevice.current.batteryLevel), often expect to get updates for every 1% change in battery. However, on iOS, the batteryLevel property only updates in steps of approximately 5%. For example, the value jumps from 1.0 (100%) to 0.95 (95%), then to 0.90 (90%), and so on. It does not report intermediate values like 0.99, 0.98, etc.
3
0
343
Jun ’25
Keyboard fails to appear after converting app to UIScene lifecycle
Getting this in log any time I try to start typing anything into a UITextField: First responder issue detected: non-key window attempting reload - allowing due to manual keyboard (first responder window is &lt;UIWindow: 0x10e016880; frame = (0 0; 1133 744); gestureRecognizers = &lt;NSArray: 0x10ba53850&gt;; backgroundColor = &lt;UIDynamicProviderColor: 0x108563370; provider = &lt;NSMallocBlock: 0x11755bd50&gt;&gt;; layer = &lt;UIWindowLayer: 0x10ba84190&gt;&gt;, key window is ) I'm suspicious of the empty "key window is" field. Everything else in the app is working fine. But I cannot figure out why this fails to show the keyboard, and no keyboard notifications are being received by the app. What could it be?
1
0
133
Jun ’25
Failed to open URL asynchronously
I have a problem with the URL schemes under iOS 18. Data is being sent from one app to another app. The amount of data varies. It can sometimes be more than 5 MB. With iOS 18, errors often occur when sending large amounts of data. The error message is: "Failed to open URL asynchronously". If I send the data once again in this case, it works. To reproduce the error quickly, I wrote two small apps. AppA sends data to AppB. AppB calls AppA and AppA sends data to AppB again. The whole thing runs in an endless loop. Code snippet: // AppA // The file to which fileUrl points contains a 4 MB string. // The string consists of only one letter “AAAAAA....” let dataStr = try String(contentsOf: fileUrl, encoding: .utf8) if let url = URL(string: "appb://receive?data=\(dataStr)") { UIApplication.shared.open(url, options: [:]) { (result) in if !result { os_log("can't open url", type: .error) } } } // AppB DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { if let returnUrl = URL(string: "appa://return") { UIApplication.shared.open(returnUrl) } } If the test is started, the error occurs approximately 15-20 times per hour. The first error occurs very quickly if the device is restarted prior to this. As soon as the error occurs, we end up in os_log(“can't open url”, type: .error) I know the possibility of exchanging the data via AppGroups, but cannot use it in our case. Tested with following devices: // The error occurs: iPhone 11 with iOS 18.4.1 iPhone SE with iOS 18.5 // The error does not occur iPhone 8 with iOS 16.7.10 iPhone 16 simulator on a M1 MacBook (macOS 15.4.1) Unfortunately, there is no other error message in the "Console" app. Except "Failed to open URL asynchronously". There were no problems at this point between iOS 12 and iOS 17. My question is now, are there new limitations to the URL schemes under iOS 18 or is it a bug?
Topic: UI Frameworks SubTopic: UIKit Tags:
3
0
262
Jun ’25
Avoid rotation in a UIViewController with two UIWindow app
Hi, I have an iPhone App with an UIWindowScene and two UIWindow's(mainWindow and alertWindow). In the mainWindow I have the whole app and it is allowed to rotate. The alertWindow is a window to show alert's to the user on the top of the screen and I do not want that the content inside rotate. I thought I may do: override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return .portrait } And override var shouldAutorotate: Bool { return false } In the rootviewcontroller of alertWindow but after doing those changes the rootviewcontroller of mainWindow does not rotate until I do any navigation. I have thought to have two UIWindowScene's (one per UIWindow) but as far I know iPhone app only supports one UIWindowScene. So, how can I avoid rotation in the viewcontroller of alertWindow without losing the rotation on rootviewcontroller of mainWindow? My viewcontroller is a UIHostingController, so I tried also to avoid from my SwiftUI view but I did not find any solution neither. Thank you in advance
3
0
183
Jun ’25
Additional Questions Regarding App Launch Timing
I found the following statement on the site TN3187: Migrating to the UIKit scene-based life cycle | Apple Developer Documentation: "Soon, all UIKit based apps will be required to adopt the scene-based life-cycle, after which your app won’t launch if you don’t. While supporting multiple scenes is encouraged, only adoption of scene life-cycle is required." In this post, you mentioned that the timing is undecided. https://developer.apple.com/forums/thread/785588 I would like to confirm the following two points additionally. Could you please confirm whether the timing when the app will not be able to launch is during an iOS update or at another specific time? This will change our response policy. Does "your app won’t launch" mean that already distributed apps will also not be able to launch? Or does it mean that newly developed apps will fail to build or be rejected during app review?
Topic: UI Frameworks SubTopic: UIKit Tags:
2
0
362
Jun ’25
UICollectionView with orthogonal (horizontal) section not calling touchesShouldCancel(in:)
I have a UICollectionView with horizontally scrolling sections. In the cell I have a UIButton. I need to cancel the touches when the user swipes horizontally but it does not work. touchesShouldCancel(in:) is only called when swiping vertically over the UIButton, not horizontally. Is there a way to make it work? Sample code below import UIKit class ConferenceVideoSessionsViewController: UIViewController { let videosController = ConferenceVideoController() var collectionView: UICollectionView! = nil var dataSource: UICollectionViewDiffableDataSource <ConferenceVideoController.VideoCollection, ConferenceVideoController.Video>! = nil var currentSnapshot: NSDiffableDataSourceSnapshot <ConferenceVideoController.VideoCollection, ConferenceVideoController.Video>! = nil static let titleElementKind = "title-element-kind" override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "Conference Videos" configureHierarchy() configureDataSource() } } extension ConferenceVideoSessionsViewController { func createLayout() -> UICollectionViewLayout { let sectionProvider = { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)) let item = NSCollectionLayoutItem(layoutSize: itemSize) // if we have the space, adapt and go 2-up + peeking 3rd item let groupFractionalWidth = CGFloat(layoutEnvironment.container.effectiveContentSize.width > 500 ? 0.425 : 0.85) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(groupFractionalWidth), heightDimension: .absolute(200)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) let section = NSCollectionLayoutSection(group: group) section.orthogonalScrollingBehavior = .continuous section.interGroupSpacing = 20 section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 20) return section } let config = UICollectionViewCompositionalLayoutConfiguration() config.interSectionSpacing = 20 let layout = UICollectionViewCompositionalLayout( sectionProvider: sectionProvider, configuration: config) return layout } } extension ConferenceVideoSessionsViewController { func configureHierarchy() { collectionView = MyUICollectionView(frame: .zero, collectionViewLayout: createLayout()) collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.backgroundColor = .systemBackground view.addSubview(collectionView) NSLayoutConstraint.activate([ collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), collectionView.topAnchor.constraint(equalTo: view.topAnchor), collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) collectionView.canCancelContentTouches = true } func configureDataSource() { let cellRegistration = UICollectionView.CellRegistration <ConferenceVideoCell, ConferenceVideoController.Video> { (cell, indexPath, video) in // Populate the cell with our item description. cell.buttonView.setTitle("Push, hold and swipe", for: .normal) cell.titleLabel.text = video.title } dataSource = UICollectionViewDiffableDataSource <ConferenceVideoController.VideoCollection, ConferenceVideoController.Video>(collectionView: collectionView) { (collectionView: UICollectionView, indexPath: IndexPath, video: ConferenceVideoController.Video) -> UICollectionViewCell? in // Return the cell. return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: video) } currentSnapshot = NSDiffableDataSourceSnapshot <ConferenceVideoController.VideoCollection, ConferenceVideoController.Video>() videosController.collections.forEach { let collection = $0 currentSnapshot.appendSections([collection]) currentSnapshot.appendItems(collection.videos) } dataSource.apply(currentSnapshot, animatingDifferences: false) } } class MyUICollectionView: UICollectionView { override func touchesShouldCancel(in view: UIView) -> Bool { print("AH: touchesShouldCancel view \(view.description)") if view is MyUIButton { return true } return false } } final class MyUIButton: UIButton { } class ConferenceVideoCell: UICollectionViewCell { static let reuseIdentifier = "video-cell-reuse-identifier" let buttonView = MyUIButton() let titleLabel = UILabel() override init(frame: CGRect) { super.init(frame: frame) configure() } required init?(coder: NSCoder) { fatalError() } } extension ConferenceVideoCell { func configure() { buttonView.translatesAutoresizingMaskIntoConstraints = false titleLabel.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(buttonView) contentView.addSubview(titleLabel) titleLabel.font = UIFont.preferredFont(forTextStyle: .caption1) titleLabel.adjustsFontForContentSizeCategory = true buttonView.layer.borderColor = UIColor.black.cgColor buttonView.layer.borderWidth = 1 buttonView.layer.cornerRadius = 4 buttonView.backgroundColor = UIColor.systemPink let spacing = CGFloat(10) NSLayoutConstraint.activate([ buttonView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), buttonView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), buttonView.topAnchor.constraint(equalTo: contentView.topAnchor), titleLabel.topAnchor.constraint(equalTo: buttonView.bottomAnchor, constant: spacing), titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) ]) } }
Topic: UI Frameworks SubTopic: UIKit Tags:
1
0
114
Jun ’25
How to replace layoutManager with textLayoutManager for a flexible dynamic height UITextView
In order to create a UITextView like that of the Messages app whose height grows to fits its contents (number of lines), I subclassed UITextView and customized the intrinsicContentSize like so: override var intrinsicContentSize: CGSize { var size = super.intrinsicContentSize if size.height == UIView.noIntrinsicMetric { layoutManager.glyphRange(for: textContainer) size.height = layoutManager.usedRect(for: textContainer).height + textContainerInset.top + textContainerInset.bottom } return size } As noted at WWDC, accessing layoutManager will force TextKit 1, we should instead use textLayoutManager. How can this code be migrated to support TextKit 2?
3
0
250
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:
Replies
1
Boosts
0
Views
149
Activity
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:
Replies
1
Boosts
2
Views
486
Activity
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)
Replies
0
Boosts
0
Views
278
Activity
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?
Replies
1
Boosts
0
Views
158
Activity
Jun ’25
How can i enable memoji sticker in system emoji keyboard while keep allowsEditingTextAttributes == NO
I was trying to adapt memoji in my app which write by objective-c.I have a textView for user input and I need to keep allowsEditingTextAttributes == NO for some reason.Is there any other way to show memoji sticker in system emoji keyboard?Thanks!
Replies
0
Boosts
0
Views
212
Activity
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.
Replies
0
Boosts
0
Views
137
Activity
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:
Replies
0
Boosts
0
Views
188
Activity
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) } }
Replies
2
Boosts
0
Views
1k
Activity
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.
Replies
0
Boosts
0
Views
216
Activity
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)
Replies
2
Boosts
0
Views
342
Activity
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!
Replies
3
Boosts
0
Views
569
Activity
Jun ’25
Xcode 26 beta: 'Building the menu bar using a storyboard is no longer supported for iOS and Mac Catalyst apps. Please migrate to the UIMenuBuilder or Commands APIs.'
Support for menus in Storyboards is yanked without ever being deprecated (to my knowledge)? Really? WTF? This is a major step backwards, Apple. So nice to have to spend a month rewriting my app after WWDC each year. Re-creating a complex menu hierarchy in code is exactly what I wanted to do. Ugh.
Replies
4
Boosts
1
Views
358
Activity
Jun ’25
iOS Battery Percentage Granularity Issue
When developing an iOS app that monitors or transmits the battery percentage (using UIDevice.current.batteryLevel), often expect to get updates for every 1% change in battery. However, on iOS, the batteryLevel property only updates in steps of approximately 5%. For example, the value jumps from 1.0 (100%) to 0.95 (95%), then to 0.90 (90%), and so on. It does not report intermediate values like 0.99, 0.98, etc.
Replies
3
Boosts
0
Views
343
Activity
Jun ’25
Keyboard fails to appear after converting app to UIScene lifecycle
Getting this in log any time I try to start typing anything into a UITextField: First responder issue detected: non-key window attempting reload - allowing due to manual keyboard (first responder window is &lt;UIWindow: 0x10e016880; frame = (0 0; 1133 744); gestureRecognizers = &lt;NSArray: 0x10ba53850&gt;; backgroundColor = &lt;UIDynamicProviderColor: 0x108563370; provider = &lt;NSMallocBlock: 0x11755bd50&gt;&gt;; layer = &lt;UIWindowLayer: 0x10ba84190&gt;&gt;, key window is ) I'm suspicious of the empty "key window is" field. Everything else in the app is working fine. But I cannot figure out why this fails to show the keyboard, and no keyboard notifications are being received by the app. What could it be?
Replies
1
Boosts
0
Views
133
Activity
Jun ’25
Failed to open URL asynchronously
I have a problem with the URL schemes under iOS 18. Data is being sent from one app to another app. The amount of data varies. It can sometimes be more than 5 MB. With iOS 18, errors often occur when sending large amounts of data. The error message is: "Failed to open URL asynchronously". If I send the data once again in this case, it works. To reproduce the error quickly, I wrote two small apps. AppA sends data to AppB. AppB calls AppA and AppA sends data to AppB again. The whole thing runs in an endless loop. Code snippet: // AppA // The file to which fileUrl points contains a 4 MB string. // The string consists of only one letter “AAAAAA....” let dataStr = try String(contentsOf: fileUrl, encoding: .utf8) if let url = URL(string: "appb://receive?data=\(dataStr)") { UIApplication.shared.open(url, options: [:]) { (result) in if !result { os_log("can't open url", type: .error) } } } // AppB DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { if let returnUrl = URL(string: "appa://return") { UIApplication.shared.open(returnUrl) } } If the test is started, the error occurs approximately 15-20 times per hour. The first error occurs very quickly if the device is restarted prior to this. As soon as the error occurs, we end up in os_log(“can't open url”, type: .error) I know the possibility of exchanging the data via AppGroups, but cannot use it in our case. Tested with following devices: // The error occurs: iPhone 11 with iOS 18.4.1 iPhone SE with iOS 18.5 // The error does not occur iPhone 8 with iOS 16.7.10 iPhone 16 simulator on a M1 MacBook (macOS 15.4.1) Unfortunately, there is no other error message in the "Console" app. Except "Failed to open URL asynchronously". There were no problems at this point between iOS 12 and iOS 17. My question is now, are there new limitations to the URL schemes under iOS 18 or is it a bug?
Topic: UI Frameworks SubTopic: UIKit Tags:
Replies
3
Boosts
0
Views
262
Activity
Jun ’25
Avoid rotation in a UIViewController with two UIWindow app
Hi, I have an iPhone App with an UIWindowScene and two UIWindow's(mainWindow and alertWindow). In the mainWindow I have the whole app and it is allowed to rotate. The alertWindow is a window to show alert's to the user on the top of the screen and I do not want that the content inside rotate. I thought I may do: override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return .portrait } And override var shouldAutorotate: Bool { return false } In the rootviewcontroller of alertWindow but after doing those changes the rootviewcontroller of mainWindow does not rotate until I do any navigation. I have thought to have two UIWindowScene's (one per UIWindow) but as far I know iPhone app only supports one UIWindowScene. So, how can I avoid rotation in the viewcontroller of alertWindow without losing the rotation on rootviewcontroller of mainWindow? My viewcontroller is a UIHostingController, so I tried also to avoid from my SwiftUI view but I did not find any solution neither. Thank you in advance
Replies
3
Boosts
0
Views
183
Activity
Jun ’25
Additional Questions Regarding App Launch Timing
I found the following statement on the site TN3187: Migrating to the UIKit scene-based life cycle | Apple Developer Documentation: "Soon, all UIKit based apps will be required to adopt the scene-based life-cycle, after which your app won’t launch if you don’t. While supporting multiple scenes is encouraged, only adoption of scene life-cycle is required." In this post, you mentioned that the timing is undecided. https://developer.apple.com/forums/thread/785588 I would like to confirm the following two points additionally. Could you please confirm whether the timing when the app will not be able to launch is during an iOS update or at another specific time? This will change our response policy. Does "your app won’t launch" mean that already distributed apps will also not be able to launch? Or does it mean that newly developed apps will fail to build or be rejected during app review?
Topic: UI Frameworks SubTopic: UIKit Tags:
Replies
2
Boosts
0
Views
362
Activity
Jun ’25
UICollectionView with orthogonal (horizontal) section not calling touchesShouldCancel(in:)
I have a UICollectionView with horizontally scrolling sections. In the cell I have a UIButton. I need to cancel the touches when the user swipes horizontally but it does not work. touchesShouldCancel(in:) is only called when swiping vertically over the UIButton, not horizontally. Is there a way to make it work? Sample code below import UIKit class ConferenceVideoSessionsViewController: UIViewController { let videosController = ConferenceVideoController() var collectionView: UICollectionView! = nil var dataSource: UICollectionViewDiffableDataSource <ConferenceVideoController.VideoCollection, ConferenceVideoController.Video>! = nil var currentSnapshot: NSDiffableDataSourceSnapshot <ConferenceVideoController.VideoCollection, ConferenceVideoController.Video>! = nil static let titleElementKind = "title-element-kind" override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "Conference Videos" configureHierarchy() configureDataSource() } } extension ConferenceVideoSessionsViewController { func createLayout() -> UICollectionViewLayout { let sectionProvider = { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)) let item = NSCollectionLayoutItem(layoutSize: itemSize) // if we have the space, adapt and go 2-up + peeking 3rd item let groupFractionalWidth = CGFloat(layoutEnvironment.container.effectiveContentSize.width > 500 ? 0.425 : 0.85) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(groupFractionalWidth), heightDimension: .absolute(200)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) let section = NSCollectionLayoutSection(group: group) section.orthogonalScrollingBehavior = .continuous section.interGroupSpacing = 20 section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 20) return section } let config = UICollectionViewCompositionalLayoutConfiguration() config.interSectionSpacing = 20 let layout = UICollectionViewCompositionalLayout( sectionProvider: sectionProvider, configuration: config) return layout } } extension ConferenceVideoSessionsViewController { func configureHierarchy() { collectionView = MyUICollectionView(frame: .zero, collectionViewLayout: createLayout()) collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.backgroundColor = .systemBackground view.addSubview(collectionView) NSLayoutConstraint.activate([ collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), collectionView.topAnchor.constraint(equalTo: view.topAnchor), collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) collectionView.canCancelContentTouches = true } func configureDataSource() { let cellRegistration = UICollectionView.CellRegistration <ConferenceVideoCell, ConferenceVideoController.Video> { (cell, indexPath, video) in // Populate the cell with our item description. cell.buttonView.setTitle("Push, hold and swipe", for: .normal) cell.titleLabel.text = video.title } dataSource = UICollectionViewDiffableDataSource <ConferenceVideoController.VideoCollection, ConferenceVideoController.Video>(collectionView: collectionView) { (collectionView: UICollectionView, indexPath: IndexPath, video: ConferenceVideoController.Video) -> UICollectionViewCell? in // Return the cell. return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: video) } currentSnapshot = NSDiffableDataSourceSnapshot <ConferenceVideoController.VideoCollection, ConferenceVideoController.Video>() videosController.collections.forEach { let collection = $0 currentSnapshot.appendSections([collection]) currentSnapshot.appendItems(collection.videos) } dataSource.apply(currentSnapshot, animatingDifferences: false) } } class MyUICollectionView: UICollectionView { override func touchesShouldCancel(in view: UIView) -> Bool { print("AH: touchesShouldCancel view \(view.description)") if view is MyUIButton { return true } return false } } final class MyUIButton: UIButton { } class ConferenceVideoCell: UICollectionViewCell { static let reuseIdentifier = "video-cell-reuse-identifier" let buttonView = MyUIButton() let titleLabel = UILabel() override init(frame: CGRect) { super.init(frame: frame) configure() } required init?(coder: NSCoder) { fatalError() } } extension ConferenceVideoCell { func configure() { buttonView.translatesAutoresizingMaskIntoConstraints = false titleLabel.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(buttonView) contentView.addSubview(titleLabel) titleLabel.font = UIFont.preferredFont(forTextStyle: .caption1) titleLabel.adjustsFontForContentSizeCategory = true buttonView.layer.borderColor = UIColor.black.cgColor buttonView.layer.borderWidth = 1 buttonView.layer.cornerRadius = 4 buttonView.backgroundColor = UIColor.systemPink let spacing = CGFloat(10) NSLayoutConstraint.activate([ buttonView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), buttonView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), buttonView.topAnchor.constraint(equalTo: contentView.topAnchor), titleLabel.topAnchor.constraint(equalTo: buttonView.bottomAnchor, constant: spacing), titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), titleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor) ]) } }
Topic: UI Frameworks SubTopic: UIKit Tags:
Replies
1
Boosts
0
Views
114
Activity
Jun ’25
How to replace layoutManager with textLayoutManager for a flexible dynamic height UITextView
In order to create a UITextView like that of the Messages app whose height grows to fits its contents (number of lines), I subclassed UITextView and customized the intrinsicContentSize like so: override var intrinsicContentSize: CGSize { var size = super.intrinsicContentSize if size.height == UIView.noIntrinsicMetric { layoutManager.glyphRange(for: textContainer) size.height = layoutManager.usedRect(for: textContainer).height + textContainerInset.top + textContainerInset.bottom } return size } As noted at WWDC, accessing layoutManager will force TextKit 1, we should instead use textLayoutManager. How can this code be migrated to support TextKit 2?
Replies
3
Boosts
0
Views
250
Activity
Jun ’25
OS 26: Mini Keyboard Bar Missing with Hardware Keyboard
In iOS 26, the mini keyboard bar does not consistently appear when typing with a hardware keyboard. This behavior differs from iOS 18, where the bar was always visible. See screenshots:
Replies
2
Boosts
1
Views
263
Activity
Jun ’25