I have a question about how UIKit expects us to handle interaction events at scale.
From what I understand so far:
For UIControls (UIButton, UISwitch, UITextField, etc.), we explicitly register with addTarget(_:action:for:).
For gestures, we add UIGestureRecognizer instances to views.
For UIView subclasses, we can override touch methods like touchesBegan/touchesEnded.
All of this must be done on the main thread, since UIKit isn’t thread-safe.
Now here’s my main concern
If I have a complex UI with hundreds or thousands of widgets, am I expected to perform these registrations individually for each widget and each high-level event (tap, long press, editing changed, etc.)?
Or does UIKit provide a more centralized mechanism?
In short: Is per-widget, per-event registration the “normal” UIKit approach, or are there best practices for scaling event handling without writing thousands of addTarget or addGestureRecognizer calls?
Thanks!
UIKit
RSS for tagConstruct and manage graphical, event-driven user interfaces for iOS or tvOS apps using UIKit.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
All of these issues appear when the search controller is set on the view controller's navigationItem and the search controller's searchBar has its scopeButtonTitles set.
So far the following issues are affecting my app on iOS/iPadOS 26 as of beta 7:
When the scopeBarActivation of UISearchController is set to .onSearchActivation, the preferredSearchBarPlacement of the navigationItem is set to .integratedButton, and the searchBarPlacementAllowsToolbarIntegration is set to false (forcing the search icon to appear in the nav bar), on both iPhones and iPads, the scope buttons never appear. They don't appear when the search is activated. They don't appear when any text is entered into the search bar. FB19771313
I attempted to work around that issue by setting the scopeBarActivation to .manual. I then show the scope bar in the didPresentSearchController delegate method and hide the scope bar in the willDismissSearchController. On an iPhone this works though the display is a bit clunky. On an iPad, the scope bar does appear via the code in didPresentSearchController, but when any scope bar button is tapped, the search controller is dismissed. This happens when the app is horizontally regular. When the app on the iPad is horizontally compact, the buttons work but the search bar's text is not correctly aligned within the search bar. Quite the mess really. I still need to post a bug report for this issue. But if issue 1 above is fixed then I don't need this workaround.
When the scopeBarActivation of UISearchController is set to .onSearchActivation, the preferredSearchBarPlacement of the navigationItem is set to .stacked, and the hidesSearchBarWhenScrolling property of the navigationItem is set to false (always show the search bar), and this is all used in a UITableViewController, then upon initial display of the view controller on an iPhone or iPad, you are unable to tap on the first row of the table view except on the very bottom of the row. The currently hidden scope bar is stealing the touches. If you activate and then cancel the search (making the scope bar appear and then disappear) then you are able to tap on the first row as expected. The initially hidden scope bar also bleeds through the first row of the table. It's faint but you can tell it's not quite right. Again, this is resolved by activating and then canceling the search once. FB17888632
When the scopeBarActivation of UISearchController is set to .onSearchActivation, the preferredSearchBarPlacement of the navigationItem is set to integrated or .integratedButton, and the toolbar is shown, then on iPhones (where the search bar/icon appears in the toolbar) the scope buttons appear (at the top of the screen) the first time the search is activated. But if you cancel the search and then activate it again, the search bar never appears a second (or later) time. On an iPad the search bar/icon appears in the nav bar and you end up with the same issue as #1 above. FB17890125
Issues 3 and 4 were reported against beta 1 and still haven't been fixed. But if issue 1 is resolved on iPhone, iPad, and Mac (via Mac Catalyst), then I personally won't be affected by issues 2, 3, or 4 any more (but of course all 4 issues need to be fixed). And by resolved, I mean that the scope bar appears and disappears when it is supposed to each and every time the search is activated and cancelled (not just the first time). The scope bar doesn't interfere with touch events upon initial display of the view controller. And there are no visual glitches no matter what the horizontal size class is on an iPad.
I really hope the UIKit team can get these resolved before iOS/iPadOS 26 GM.
A bottom accessory view is set on the UITabBarController. When changing the window size either by dragging the resizing grip or when going to portrait mode, the accessory view shrinks down to the smaller width. When resizing the window to make it larger, the accessory view doesn’t resize to the full available width.
During a workshop setup by Apple, folks from Apple told me that the view set as the content view of the UITabAccessory should not have its frame changed, either by using Auto Layout or by setting the frame. It seems logical since the view in the bottom accessory is supposed to resize accordingly to several factors, like when going inline inside the tab bar.
Am I missing something? Maybe there is additional setup required not mentioned in the dedicated video.
Feedback is FB19017330. It contains a sample project and videos demonstrating the issue. Reproduced on Xcode 26 beta 6 (17A5305f)
I copy and paste the sample code that setups the bottom accessory for good measure.
final class MiniPlayer: UIView {
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
label.text = "Mini Player"
label.numberOfLines = 0
label.textAlignment = .center
addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: centerXAnchor),
label.centerYAnchor.constraint(equalTo: centerYAnchor)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - View Controller
class ViewController: UIViewController {
let rootTabBarController = UITabBarController()
let miniPlayer = MiniPlayer()
override func viewDidLoad() {
super.viewDidLoad()
let items: [UITab] = [
UITab(title: "Tab 1", image: UIImage(systemName: "archivebox.fill"), identifier: "tab-1") { _ in
UIViewController()
},
UITab(title: "Tab 2", image: UIImage(systemName: "books.vertical.fill"), identifier: "tab-2") { _ in
UIViewController()
},
UISearchTab { _ in
UIViewController()
}
]
rootTabBarController.tabs = items
rootTabBarController.view.backgroundColor = .secondarySystemBackground
view.addSubview(rootTabBarController.view)
rootTabBarController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
rootTabBarController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
rootTabBarController.view.topAnchor.constraint(equalTo: view.topAnchor),
rootTabBarController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
rootTabBarController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
rootTabBarController.bottomAccessory = UITabAccessory(contentView: miniPlayer)
}
}
According to the WWDC25 Presentation Track workouts with HealthKit on iOS and iPadOS, there is supposed to be a new property for restoring an active workout after a crash on iOS/iPadOS. The developer documentation also supports this. However, this property does not seem to exist in the latest Xcode 26 beta, even in projects targeting iOS 26.0 as the minimum version.
Am I missing something? Has this property not been made available yet? It is actually looking like all of the new iOS 26.0 properties are missing UIScene.ConnectionOptions on my system.
This issue was in the first iOS 26 beta and it still there with Xcode 26 beta 6 (17A5305f). Feedback is FB18581605 and contains sample project to reproduce the issue.
I assign a target and action to a UISlider for the UIControl.Event.valueChanged value:
addTarget(self, action: #selector(sliderValueDidChange), for: .valueChanged)
Here’s the function.
@objc
func sliderValueDidChange(_ sender: UISlider, event: UIEvent) {
print(event)
}
When printing the event value, there is a crash. When checking the event value with lldb, it appears uninitialized.
I tried playing with SF Symbols variable value draw from the first iOS 26 beta and it has never been working, whereas the SwiftUI version works properly.
I just copied and pasted the code from the related video What’s new in SF Symbols 7 and it simply doesn't work (screenshot attached). Full view controller code is at the end of the post.
imageView.image = UIImage(systemName: "thermometer.high", variableValue: 0.5)
imageView.preferredSymbolConfiguration = UIImage.SymbolConfiguration(variableValueMode: .draw)
Am I missing something? Or is it still not ready? I reproduced the issue with Xcode 26 beta 6 (17A5305f). The release candidates are approaching fast and I am worried this will not be working by then.
The feedback ID is FB18898182.
class ViewController: UIViewController {
let imageView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = UIImage(systemName: "thermometer.high", variableValue: 0.5)
imageView.preferredSymbolConfiguration = UIImage.SymbolConfiguration(variableValueMode: .draw)
.applying(UIImage.SymbolConfiguration(font: .systemFont(ofSize: 40)))
view.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
}
Hi everyone!
My Apple Watch app has relied for years on the WKApplication.scheduleBackgroundRefresh(...) method to keep the app updated in the background. The system would reliably trigger WKApplicationDelegate.handle(_:), where I would then schedule the next refresh task (usually 15 minutes later).
As stated in the documentation, as long as there is a complication on the watch face, these background tasks should run at a relatively stable frequency.
However, this approach seems to have stopped working on watchOS 26. I no longer receive any WKApplicationRefreshBackgroundTask at all. Has anyone else experienced this issue?
The default cell height is 44pt in iOS 18 and 52pt in iOS 26. I'm trying to reduce the height back to 44pt in one screen that needs to fit as much content on screen as possible. How do you do that when using UIListContentConfiguration?
I expected this would do the trick but alas it doesn't reduce the cell height.
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Item> { cell, indexPath, item in
cell.contentConfiguration = {
var config = UIListContentConfiguration.valueCell()
config.text = "Title"
config.secondaryText = "Value"
// This only removes horizontal margins, does not change vertical margins
config.axesPreservingSuperviewLayoutMargins = []
config.directionalLayoutMargins = .zero
return config
}()
}
Applicaiton is built with WebKit and native camera view controller .
Issue is seen always when presenting a UIVideoEditorController class on top of MainVC(webKit).
Please refer the attached image
Code:
self.videoEditor.modalPresentationStyle = UIModalPresentationFullScreen;
[viewController presentViewController:self.videoEditor animated: YES completion:nil];
I’m working with UIButton and finding different examples for event handling. Currently, I have a single action method like this, which receives the sender and the UIEvent:
@objc func buttonHandler(_ sender: UIButton, forEvent event: UIEvent) {
if let touches = event.allTouches, let touch = touches.first {
switch touch.phase {
case .began: print("TouchDown")
case .ended:
if sender.bounds.contains(touch.location(in: sender)) {
print("TouchUpInside")
} else {
print("TouchUpOutside")
}
case .cancelled: print("TouchCancel")
default: break
}
}
if event.type == .presses {
print("PrimaryActionTriggered")
}
}
Is this considered best/recommended practice in UIKit, or should I use separate selector methods for each event type (e.g. .touchDown, .touchUpInside, .touchUpOutside) using addTarget(_:action:for:)?
Are there any advantages or disadvantages to using a single handler with UIEvent versus multiple selectors for UIControlEvents?
Thanks in advance!
I’m working with UIButton and I’d like to register multiple UIControl.Events (e.g. .touchUpInside, .touchDown, .touchCancel, .primaryActionTriggered) using the same selector.
For example:
button.addTarget(self,
action: #selector(handleButtonEvent(_:forEvent:)),
for: [.touchUpInside, .touchDown, .touchCancel, .primaryActionTriggered])
@objc func handleButtonEvent(_ sender: UIButton, forEvent event: UIEvent) {
// How do I tell which UIControl.Event triggered this?
}
From my understanding:
If I use the single-parameter version (@objc func handleButtonEvent(_ sender: UIButton)), I can’t distinguish which event fired.
If I use the two-parameter version with UIEvent, I can inspect touch.phase or event.type, but that feels indirect.
Questions:
Is there a recommended way to directly know which UIControl.Event caused the selector to fire?
Is sharing a single selector across multiple control events considered a good practice, or is it more common to register separate selectors per event?
Would appreciate guidance on what Apple recommends here.
I’m trying to detect a double-tap action on a UIButton. There seem to be two possible approaches:
Using a UITapGestureRecognizer with numberOfTapsRequired = 2.
Using the .touchDownRepeat event of UIControl.Event.
What is the recommended approach for reliably handling double-taps on UIButton? Are there any practical differences in terms of behavior, performance, or best practices between these two methods?
Additionally, I noticed that UIControl.Event defines a large set of events (like .editingChanged, .valueChanged, etc.).
Can all these events be applied to any UIControl subclass such as UIButton, or are they only valid for specific controls like UITextField, UISlider, etc.?
If not all events are supported by all controls, what is the rationale behind exposing them under a shared UIControl.Event enum?
Thanks in advance!
The isEnabled property on UITabBarItem no longer disables tab bar items as expected in iOS 26. Setting tabBarItem.isEnabled = false on a view controller has no visual or functional effect - the tab remains interactive and doesn't show the disabled state.
Topic:
UI Frameworks
SubTopic:
UIKit
I'm using multiple scenes in my iPad app.
When I open a new scene from my main window, that new window is always the same size as the previous window.
When I make the main window very small and then create a new scene, that new window is also tiny. When I make the main window very big, you guessed it.
UIWindowScene.sizeRestrictions does not seem to help here. How can I give new windows a default size (it's okay if they're resizable after presenting)? This is such a weird behavior.
Video of the problem in action: https://mastodon.social/@nicoreese/115033539035249909
It appears from my testing that the following is true:
If you close all windows of an app, the app terminates entirely. Minimizing the last window sends it into multitasking. This is different from previous behavior and might have implications on your app's logic.
If you close the last window and quickly tap the app icon again, that same window will come back. Waiting a second before tapping the app icon will bring up the main window (likely because by that point the app was terminated and relaunched).
Is this expected behavior? I did not see any announcement of this.
I find this a bit counterintuitive and would presume that closing the last window would just send the app to multitasking, just like previously. Quitting the app should be reserved by swiping up on it in the multitasking UI or with a new context menu command.
An app with a UIToolbar pinned to the bottom of a view controllers view extends to the screen edge (behind the safe area) when run on iOS 18.X but not iOS 26.
This UIToolbar is set as constrained to the safeAreaLayoutGuide with the following constraints.
self.view.addConstraints([
bottomToolbar.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
bottomToolbar.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
bottomToolbar.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor),
bottomToolbar.heightAnchor.constraint(greaterThanOrEqualToConstant: 44.0)
])
This is especially noticeable when the UIToolbar background color differs from the view background color.
This occurs on iOS 26 beta 6, app built with Xcode 26 beta 5.
I've posted a Feedback FB19664903 including a minimal Xcode workspace that reproduces this issue.
Anyone suggestions would be appreciated ... this definitely seems like a regression.
A UISegmentedControl as a UIBarButtonItem (customView) is ignoring selectedSegmentTintColor on iOS 26, works fine on prior versions
I've submitted FB19660941 with a minimal Xcode workspace to reproduce the issue.
Has anyone else seen this and identified a workaround?
An iOS app has a UINavigationController with a UINavigationBar that is non-translucent (e.g. black). When performing a push (or pop) to navigate to or from another UIViewController the UIBarButtonItems on the navigation bar are flashing a white background. With a dark navigation bar this is very noticeable and not desirable.
This only occurs when run on iOS 26 and is related to Liquid Glass
I've created FB19660024 with a minimal Xcode workspace to reproduce, along with a video showing the behavior.
This is a cosmetic bug, not affecting functionality, but is a very undesirable effect on apps with dark and non-translucent navigation bars.
Has anyone else seen this and found a workaround?
Can anyone show a simple code snippet about the usage of this property?
I tried to create a UIWindow inside the main window, and set the canResizeToFitContent to true. However, when I added to it some sub view, the sub view didn't fit into the UIWindow. Actually, this sub view was located in another place far away from the UIWindow I created.
Additionally, The documentation says, the content should be constraint-based. What does 'constraint-based' mean?
Topic:
UI Frameworks
SubTopic:
UIKit
Hi everyone,
I'm not an experienced developer. I'm interested in the low-latency related APIs in UIUpdateLink, but I failed to write even a minimal demo that works.
UIUpdateInfo.isImmediatePresentationExpected is always false here. My understanding must be wrong. I've totally no idea so I'm asking for help here. I appreciate anyone who gives suggestions of any kind.
Here's my (failed) demo about tracking touch inputs (of the 1st finger) and draw some shape at that place:
import UIKit
class ContentUIView: UIView {
// MARK: - About UIUpdateLink and drawing
required init?(coder: NSCoder) {
super.init(coder: coder)
initializeUpdateLink()
}
override init(frame: CGRect) {
super.init(frame: frame)
initializeUpdateLink()
}
private func initializeUpdateLink() {
self.updateLink = UIUpdateLink(view: self)
self.updateLink.addAction(to: .beforeCADisplayLinkDispatch,
target: self,
selector: #selector(update))
self.updateLink.wantsImmediatePresentation = true
self.updateLink.isEnabled = true
}
@objc func update(updateLink: UIUpdateLink,
updateInfo: UIUpdateInfo) {
print(updateInfo.isImmediatePresentationExpected) // FIXME: Why always false?
CATransaction.begin()
defer { CATransaction.commit() }
layer.setNeedsDisplay()
layer.displayIfNeeded()
}
override func draw(_ rect: CGRect) {
// FIXME: Any way to support opacity?
guard let context = UIGraphicsGetCurrentContext() else { return }
context.clear(rect)
guard let lastTouch = self.lastTouch else { return }
let location = lastTouch.location(in: self)
let circleBounds = CGRect(x: location.x - 16, y: location.y - 16, width: 32, height: 32)
context.setFillColor(.init(red: 1/2, green: 1/2, blue: 1/2, alpha: 1))
context.addLines(between: [])
context.fillEllipse(in: circleBounds)
}
// MARK: - Touch input
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
guard lastTouch == nil else { return }
lastTouch = touches.first
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
guard let lastTouch, touches.contains(lastTouch) else { return }
self.lastTouch = nil
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
self.touchesEnded(touches, with: event)
}
private var lastTouch: UITouch?
private var updateLink: UIUpdateLink!
}
#Preview { ContentUIView() }
Anyway, I'm not meant to find alternative APIs and I'd be willing to know what it can't do.