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

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
279
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
204
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 <UIWindow: 0x10e016880; frame = (0 0; 1133 744); gestureRecognizers = <NSArray: 0x10ba53850>; backgroundColor = <UIDynamicProviderColor: 0x108563370; provider = <NSMallocBlock: 0x11755bd50>>; layer = <UIWindowLayer: 0x10ba84190>>, 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
85
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
217
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
131
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
283
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
87
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
163
Jun ’25
How to listen for Locale change event in iOS using NSLocale.currentLocaleDidChangeNotification?
I have the following function private func SetupLocaleObserver () { NotificationCenter.default.addObserver ( forName: NSLocale.currentLocaleDidChangeNotification, object: nil, queue: .main ) {_ in print ("Locale changed to: \(Locale.current.identifier)"); } } I call this function inside the viewDidLoad () method of my view controller. The expectation was that whenever I change the system or app-specific language preference, the locale gets changed, and this change triggers my closure which should print "Locale changed to: " on the console. However, the app gets terminated with a SIGKILL whenever I change the language from the settings. So, it is observed that sometimes my closure runs, while most of the times it does not run - maybe the app dies even before the closure is executed. So, the question is, what is the use of this particular notification if the corresponding closure isn't guaranteed to be executed before the app dies? Or am I using it the wrong way?
1
0
140
Jun ’25
iOS 14 UIPickerView Selected View Background Color
I have noticed that in iOS 14 the UIPickerView has by default a light grey background on the selected Row like shown here. https://developer.apple.com/design/human-interface-guidelines/ios/controls/pickers/ I noticed also that pickerView.showsSelectionIndicator is deprecated on iOS 14. Is there a way to change the background color to white and add separators to achieve a pre iOS 14 UIPickerView style? Thank you
9
0
11k
Jun ’25
Enabling Main Thread Checker in Xcode May Cause Category Method Implementation Conflicts for UI-Related Classes
​Environment​: Xcode Version: 16.0 (latest stable release) iOS Version: 18.3.1 Devices: physical devices Configuration: Main Thread Checker enabled (Edit Scheme &amp;gt; Run &amp;gt; Diagnostics) ​Issue Description​ When the ​Main Thread Checker​ is enabled, methods defined in a UIViewController category (e.g., supportedInterfaceOrientations) fail to execute, whereas the subclass implementation of the same method works as expected. This conflicts with the normal behavior where ​both implementations should be called. ​Steps to Reproduce​ 1、Declare a category method in UIViewController+Extend.m: // UIViewController+Extend.m @implementation UIViewController (Extend) - (UIInterfaceOrientationMask)supportedInterfaceOrientations { NSLog(@"category supportedInterfaceOrientations hit"); return UIInterfaceOrientationMaskAll; } @end 2、Override the same method in a subclass ,call super methed(ViewController.m): // ViewController.m @implementation ViewController - (UIInterfaceOrientationMask)supportedInterfaceOrientations { NSLog(@"subclass called supportedInterfaceOrientations called"); return [super supportedInterfaceOrientations]; // Expected to call the category implementation } @end 3、​Expected Behavior​ (Main Thread Checker ​disabled): subclass called supportedInterfaceOrientations called category supportedInterfaceOrientations hit 4、Actual Behavior​ (Main Thread Checker ​enabled): subclass called supportedInterfaceOrientations called // category supportedInterfaceOrientations hit ​Requested Resolution​ Please investigate: 1、Why Main Thread Checker disrupts category method invocation. 2、Whether this is a broader issue affecting other UIKit categories.
1
0
103
Jun ’25
iOS 26 beta: UIResponder inputAccessoryView no longer integrates seamlessly with system keyboard
Prior to iOS 26, it was possible to design an inputAccessoryView(Controller) that would integrate seamlessly with the system keyboard, by which I mean appearing as a natural extension of the system keyboard. For example, using CYRKeyboardButton https://github.com/tmcintos/CYRKeyboardButton. To date, I have successfully used this to provide an enhanced numeric key row within my apps, which is a distinguishing feature of these apps. It took a lot of engineering and testing effort to perfect this design. However, with iOS 26 the design is completely broken due to the system keyboard UI change, which makes it impossible to display an inputAccessoryView seamlessly along the top of the system keyboard (see attached screenshots). In my opinion, it is just plain reckless for Apple to make these kinds of trivial UI changes, which break existing app designs without adding any significant value to the user experience. iOS ≤ 18.x: iOS 26 beta:
2
5
364
Jun ’25
Question about `UITextField`'s `markedTextRange` when handling Korean input
I'm currently working on implementing a character limit for Korean text input using UITextField, but I've encountered two key issues. 1. How can I determine if Korean input is complete? I understand that markedTextRange represents provisional (composing) text during multistage text input systems (such as Korean, Japanese, Chinese). While testing with Korean input, I expected markedTextRange to reflect the composing state. However, it seems that markedTextRange remains nil throughout the composition process. 2. Problems limiting character count for Korean input I’ve tried two methods to enforce a character limit. Both lead to incorrect behavior due to how Korean characters are composed. Method 1 – Before replacement: func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -&gt; Bool { guard let text = textField.text else { return true } return text.count &lt;= 5 } This checks the text length before applying the replacementString. The issue is that when the user enters a character that is meant to combine with the previous one to form a composed character, the input should result in a single, combined character. However, because the character limit check is based on the state before the replacement is applied, the second character does not get composed as expected. Method 2 – After change: textField.addTarget(self, action: #selector(editingChanged), for: .editingChanged) @objc private func editingChanged(_ sender: UITextField) { guard var text = sender.text else { return } if text.count &gt; limitCount { text.removeLast() sender.text = text } } This removes the last character if the count exceeds the limit after the change. But when a user keeps typing past the limit, the last character is overwritten by new input. I suspect this happens because the .editingChanged event occurs before the multistage input is finalized, and the final composed character is applied after that event. My understanding of the input flow: Standard input: shouldChangeCharactersIn is called replacementString is applied .editingChanged is triggered With multistage input (Korean, etc.): shouldChangeCharactersIn is called replacementString is applied .editingChanged is triggered Final composed character is inserted (after all the above) Conclusion Because both approaches lead to incorrect character count behavior with Korean input, I believe I need a new strategy. Is there an officially recommended way to handle multistage input properly with UITextField in this context? Any advice or clarification would be greatly appreciated. MacOS 15.5(24F74) Xcode 16.4 (16F6)
2
0
165
Jun ’25
UISplitViewController changes behavior of `viewControllers` property on iOS 26
I am attempting to start my application on iOS 26 with Xcode 26. It uses an UISplitViewController that is instantiated through a Storyboard. It uses the "Unspecified" style, which is a holdover from a previous version of iOS. I'm not sure if this is a bug in iOS, or if I am supposed to change it now. The viewControllers property only has the primary view controller on iOS, although it has the primary and detail view controllers on iPadOS. When I start the application on iOS 18.5, it has both primary and detail controllers on both platforms.
0
0
127
Jun ’25
Pencil "Touches" on Catalyst are interrupted with modifier
I'm working on a catalyst video editor and I'm using my wacom graphic tablet to work. The wacom input gets translated into a pencil touch. Whenever I hold down a modifier (shift, cmd etc) the touch gets ended and also ends all gestures. The mouse (indirectPointer touch) doesn't exhibit this kind of behavior. Is this expected behavior? If so is there a way to opt out? Any way to prevent this? This basically makes the typical transform gestures impossible to do when using the graphic tablet.
2
0
65
Jun ’25
Capturing self instead of using self. in switch case in DispatchQueue causes compiler error
I have an @objC used for notification. kTag is an Int constant, fieldBeingEdited is an Int variable. The following code fails at compilation with error: Command CompileSwift failed with a nonzero exit code if I capture self (I edited code, to have minimal case) @objc func keyboardDone(_ sender : UIButton) { DispatchQueue.main.async { [self] () -> Void in switch fieldBeingEdited { case kTag : break default : break } } } If I explicitly use self, it compiles, even with self captured: @objc func keyboardDone(_ sender : UIButton) { DispatchQueue.main.async { [self] () -> Void in switch fieldBeingEdited { // <<-- no need for self here case self.kTag : break // <<-- self here default : break } } } This compiles as well: @objc func keyboardDone(_ sender : UIButton) { DispatchQueue.main.async { () -> Void in switch self.fieldBeingEdited { // <<-- no need for self here case self.kTag : break // <<-- self here default : break } } } Is it a compiler bug or am I missing something ?
3
0
401
Jun ’25
iPadOS26: UITabBar doesn't switch selected state
With iPadOS26, if I create a UITabBar, and use that to switch between views, the selected state never updates. I created this simple UIViewController to demonstrate the issue: class SimpleTabBarController: UIViewController, UITabBarDelegate { let tabBar = UITabBar() let redItem = UITabBarItem(title: "Red", image: nil, tag: 0) let blueItem = UITabBarItem(title: "Blue", image: nil, tag: 1) override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white tabBar.items = [redItem, blueItem] tabBar.selectedItem = redItem tabBar.delegate = self tabBar.translatesAutoresizingMaskIntoConstraints = false view.addSubview(tabBar) NSLayoutConstraint.activate([ tabBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), tabBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), tabBar.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) ]) updateBackground(for: redItem) } func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) { updateBackground(for: item) } private func updateBackground(for item: UITabBarItem) { switch item.tag { case 0: view.backgroundColor = .systemRed case 1: view.backgroundColor = .systemBlue default: view.backgroundColor = .white } } } The tabBar didSelect item method is called, and the background color gets updated as expected, but the selected state of the UITabBar stays the same. I files a feedback for a related issue: FB17841678
1
0
262
Jun ’25