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.
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
On an iPad running iOS26, there is an issue with the numberPad keyboard
I have a UITextField with a keyboard type of .numberPad
When I first tap in the field, a new number pad with just numbers (similar to the one that shows up on iPhone) shows up.
When I tap again in the field, that number pad goes away.
When I tap in the field again, the full keyboard with numbers etc shows up (this is the one that used to always show up pre-iOS26)
Topic:
UI Frameworks
SubTopic:
UIKit
I am trying to use Zone Sharing in my SwiftUI app. I have been attempting to get the UICloudSharingController to show an initial share screen to pick users and the mechanism to send the invitation.
From the documentation, it appears that the UICloudSharingController .init(preparationHandler:) API is deprecated so I am not using that approach. Following the Apple documentation, I am creating a Zone Share and NOT saving it and presenting using the UICloudSharingController(share:container:) API. However, this presents a UI that is the 'mgmt' API for a Share. I can get to the UI I was expecting by tapping on the 'Share with More People' option, but I want to start on that screen for the user when they have not shared this before.
So, I found an example app from Apple at: https://github.com/apple/sample-cloudkit-zonesharing. It has the same behavior. So we can simply discuss this problem based on that example code.
How do I get the next View presented when tapping 'Share Group' to be the invitation for new users screen?
Here is the UI it presents initially:
And here is the UI (on the bottom half of the screen) I am trying to start the share process with:
Thanks,
Charlie
I do the majority of my test development on an iPhone 16 Pro in the iOS Simulator. As part of my UI rework to maintain compatibility with iOS 26 I decided to run on an older device with a small screen size for testing. The smallest device that supports iOS 26 is the iPhone SE 2nd Gen and 3rd Gen.
Take a look at the image below:
On the left is the iPhone SE 2nd Gen. On the right iPhone 16 Pro.
It looks like the UITabBar which is from a UITabBarController is sized too tall. The actual Tab Bar itself is 62px while the container that houses it is 83px. Yes there should be some top/bottom space to allow for the Liquid Glass Effect to overlap as it often spills outside it's bounds but this feels like far too much.
Looking at them side by side, the iPhone SE Tab Bar actually takes up more space which is not ideal for users who are working on a smaller screen to begin with and have less real estate.
It looks like the bottom space is allowable room for the 'swipe to dismiss line' however these devices use Touch ID and that line is never present. Feels like the 83px for the Tab Bar should be reduced on these devices to allow for more useable screen real estate. Is this a design oversight as iOS 26 seems to be built predominantly for Face ID Devices? Just wondering if any Apple Design Engineers can chime in to see if this will be addressed in a future beta?
The only way to change it at this stage is with a CGAffineTransform however this does not impact the '_UITabBarContainerWrapperView' that sits behind it.
Also, on that note. The '_UITabBarContainerWrapperView' sometimes seems to be clear and other times displays a solid system background color. Is there anyway to control this? Many of my views both SwiftUI and UIKit have differing behaviour. My understanding is the idea of iOS 26 is for your app's content to be visible behind the Tab Bar, however the content is obscured by '_UITabBarContainerWrapperView' in many of my views and I can not figure out how to change that.
Thanks!
When working with modal sheet views in iOS 26 I animate changes to detent size like this:
if let sheet = self.sheetPresentationController {
let newDetent = 400
sheet.animateChanges {
sheet.invalidateDetents()
sheet.detents = [.custom(resolver: { context in newDetent })]
}
}
What I have found is that when using a Touch ID Device the input detent will be smaller when the system presents it. If I set the detent size to be 400, on an iPhone 16 Pro it will be 400px but on an iPhone SE 2nd Gen it will be 366.
It seems to be consistently 34px shorter on devices with a Touch ID Square Shaped Screen. This 34px corresponds to the bottom safe area. This feels like a bug to me. I would expect the detent size to match what I assign on all devices.
I will monitor if this is fixed in a future beta but it is present as of iOS 26 Beta 5. In the meantime I have built this helper function which will then correctly size it on all devices:
static func getDetent(basedOn inputValue: Double) -> CGFloat {
if let window = UIApplication.shared.windows.first {
if window.safeAreaInsets.bottom > 0 {
// Face ID Device, Bottom Inset of 34
return inputValue
} else {
// Touch ID Device, Bottom Inset of 0
return inputValue + 34
}
} else {
// Fallback, Return Input Value
return inputValue
}
}
This impacts when animating detents as per above but also when presenting a Sheet View for the first time and assigning a custom detent.
Thanks!
For anyone interested, I put together a simple SwiftUI proof-of-concept wrapper around PaperKit:
https://blog.objectivepixel.com/posts/using-paperkit-papermarkerview-in-swiftui
Gist on Github: PaperKit SwiftUI
Topic:
UI Frameworks
SubTopic:
UIKit
Using a storyboard, I created a UIView containing an UIImageView and a UILabel, that I dragged into the navigation bar of one of my viewControllers. In my viewDidLoad I transform the view to move it down past the bounds of the navigation bar so its hidden initially
navBarMiddleView.transform = .init(translationX: 0, y: 50)
Then as the screen scrolls, I slowly move it up so it slides back into the middle of the navigationBar
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let padding: CGFloat = 70
let adjustedOffset = (scrollView.contentOffset.y - padding)
navBarMiddleView.transform = .init(translationX: 0, y: max(0, (50 - adjustedOffset)))
}
(The content is displayed in a collectionView cell as large content initially, and I want it to remain visible as the user scrolls down. So a mini view of the same data is moved up into the navigationBar)
With iOS 26 the navigationBar is applying a blur effect to this view, even when its outside the bounds of the navigationBar, meaning the content its hovering on top of is slightly obscured. I don't know why this blur is being added, how do I remove it?
I've tried the following based on recommendations from chatGPT but none have worked
self.navigationController?.navigationBar.clipsToBounds = true
self.navBarMiddleView.layer.allowsGroupOpacity = false
self.navBarMiddleView.backgroundColor = .clear
self.navBarMiddleView.isOpaque = true
self.navBarMiddleView.layer.isOpaque = true
I have my navigation bar setup with this appearence already:
let navigationBarAppearance = UINavigationBarAppearance()
navigationBarAppearance.configureWithOpaqueBackground()
navigationBarAppearance.backgroundEffect = nil
navigationBarAppearance.backgroundColor = UIColor.clear
navigationBarAppearance.shadowColor = .clear
navigationBarAppearance.titleTextAttributes = [
NSAttributedString.Key.foregroundColor: UIColor.colorNamed("Txt2"),
NSAttributedString.Key.font: UIFont.custom(ofType: .bold, andSize: 20)
]
UINavigationBar.appearance().standardAppearance = navigationBarAppearance
UINavigationBar.appearance().compactAppearance = navigationBarAppearance
UINavigationBar.appearance().scrollEdgeAppearance = navigationBarAppearance
In iOS 26, I found that editButtonItem.title cannot be programmatically updated as before.
Even when explicitly setting the title in viewWillAppear or inside setEditing(_:animated:), the text remains the default “Edit” / “☑️”.
Here is a minimal reproducible example:
import UIKit
class ViewController: UIViewController {
private let infoLabel: UILabel = {
let l = UILabel()
l.text = "uneditable"
l.font = .systemFont(ofSize: 28, weight: .medium)
l.textAlignment = .center
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
private let textField: UITextField = {
let tf = UITextField()
tf.placeholder = "This will become effective when you edit it."
tf.borderStyle = .roundedRect
tf.translatesAutoresizingMaskIntoConstraints = false
tf.isEnabled = false
return tf
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
navigationItem.title = "sample"
navigationItem.rightBarButtonItem = editButtonItem
setupLayout()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
updateEditButtonTitle()
}
private func setupLayout() {
view.addSubview(infoLabel)
view.addSubview(textField)
NSLayoutConstraint.activate([
infoLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
infoLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -40),
textField.topAnchor.constraint(equalTo: infoLabel.bottomAnchor, constant: 20),
textField.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
textField.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
textField.heightAnchor.constraint(equalToConstant: 44),
])
}
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
textField.isEnabled = editing
infoLabel.text = editing ? "editable" : "uneditable"
if editing {
textField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
}
updateEditButtonTitle()
}
private func updateEditButtonTitle() {
editButtonItem.title = isEditing ? "done" : "edit"
}
}
Expected behavior
Changing editButtonItem.title should update the displayed title of the bar button item.
Actual behavior (iOS 26)
The title remains the default “Edit” / “☑️” text, ignoring programmatic updates.
This worked in previous iOS versions. It appears that in iOS 26 the system may be overriding editButtonItem.title automatically, preventing custom titles.
Is this an intentional change, or a bug introduced in iOS 26?
Topic:
UI Frameworks
SubTopic:
UIKit
*** Terminating app due to uncaught exception 'CALayerInvalidGeometry', reason: 'CALayer position contains NaN: [nan 106.333]. Layer: <CALayer:0x15c3f2d60; position = CGPoint (0 0); bounds = CGRect (0 0; 0 57.6667); delegate = <_UIEditMenuListView: 0x162400780; frame = (0 0; 0 57.6667); anchorPoint = (30, 0); alpha = 0; layer = <CALayer: 0x15c3f2d60>>; sublayers = (<CALayer: 0x1625005a0>, <CALayer: 0x15c3f2130>); opaque = YES; allowsGroupOpacity = YES; anchorPoint = CGPoint (30 0); opacity = 0>'
Topic:
UI Frameworks
SubTopic:
UIKit
previously, setting accessoryImage would display the image on the far right. Now, it appears right next to the detailText, and the image is extremely small. I am already using the latest beta, but the problem still exists.
The app becomes unresponsive when pushing a new page. The screen is covered by the _UIParallaxOverlayView class, blocking all gestures.
Are there any scenarios where the transition animation might suddenly stop mid-process?
Or could you provide more information to help me troubleshoot this issue?
I noticed:
When the issue occurs, the FromViewController is displayed on the screen. The ToViewController also exists in the view tree, but it's not visible on the screen.
_UIParallaxOverlayView only appears on iOS 18 and above.
The animation appears to be controlled by +[UIView _fluidParallaxTransitionsEnabledWithTraitCollection:], which is _os_feature_enabled_impl("UIKit", "fluid_parallax_transitions"). Reference
I am using CPNavigationAlert and am getting a specific name for the title, so I do not have more than one variant. Sometimes, the variant title is longer, which for some reason makes the image very small.
I have tried to make sure to keep displayScale in mind:
let maximumImageSize = CPListItem.maximumImageSize
let displayScale = self.interfaceController?.carTraitCollection.displayScale ?? 2
let imageSize = CGSizeMake(maximumImageSize.width * displayScale, maximumImageSize.width * displayScale)
let image = CarPlayMapUtility().getIconAlertImage(item: item, frame: imageSize)
If the titleVariants is shorter, the image is displayed corrected. If it is longer, the image might be extremely small or not shown at all. Is this expected?
Hi,
I am trying to implement the UIMainMenuSystem for showing the menu bar in my iPad app in iOS26. I would like to be able to disable some elements when a particular UIViewController is displayed on the screen, and I can't figure out the best way to do this. I tried overriding the 'validateCommand' method in my view controller, but it doesn't seem to invoke the validation for menu items that are in the main menu.
Any tips on how to do this?
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.
When a UISearchController is placed inside a search tab, the scope buttons disappear when dismissing the search bar once. They never return. When using in any regular view controller container, like even another default tab, everything works fine. Is there something I can do to prevent this?
Video: https://mastodon.social/@nicoreese/115017696077771370
FB19587916
let homeTab = UITab(
title: "Home",
image: UIImage(systemName: "house"),
identifier: "Home"
) { _ in
UINavigationController(rootViewController: ViewController())
}
let searchTab = UISearchTab { _ in
UINavigationController(rootViewController: SearchViewController())
}
let tabBarController = UITabBarController(tabs: [
homeTab, searchTab
])
tabBarController.mode = .tabSidebar
class SearchViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .systemBackground
self.title = "Home"
let searchController = UISearchController(searchResultsController: nil)
searchController.searchBar.scopeButtonTitles = [
"Scope 1", "Scope 2"
]
searchController.searchBar.showsScopeBar = true
self.navigationItem.searchController = searchController
}
}
I would like my users to be able to switch to the search tab (in the sidebar) on iPad and immediately start typing. This is not possible. Calling becomeFirstResponder in viewDidLoad and viewWillAppear does not work. Only in viewDidAppear it does, but that comes with a significant delay between switching to the tab and the search field becoming active. Is there something else I can do?
FB19588765
let homeTab = UITab(
title: "Home",
image: UIImage(systemName: "house"),
identifier: "Home"
) { _ in
UINavigationController(rootViewController: ViewController())
}
let searchTab = UISearchTab { _ in
UINavigationController(rootViewController: SearchViewController())
}
let tabBarController = UITabBarController(tabs: [
homeTab, searchTab
])
tabBarController.mode = .tabSidebar
class SearchViewController: UIViewController {
let searchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .systemBackground
self.title = "Search"
self.navigationItem.searchController = searchController
self.navigationItem.preferredSearchBarPlacement = .integratedCentered
searchController.becomeFirstResponder() // Does not work.
searchController.searchBar.becomeFirstResponder() // Does not work.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
searchController.searchBar.becomeFirstResponder() // Does not work.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
searchController.searchBar.becomeFirstResponder() // Works. But comes with a significant delay.
}
}
When using UISearchController, scope buttons never appear. On iPadOS 18 this works fine. This is a showstopper bug for me and my app. Is there anything I can do about it?
Sample project is attached to feedback FB19587622.
In the WWDC 2025 session "Build a UIKit app with the with the new design", at the 23:22 mark, the presenter says:
And finally, when you no longer need the glass on screen animate it out by setting the effect to nil.
The video shows a UIVisualEffectView whose effect is set to a UIGlassEffect animating away as its effect is set to nil. But when I do this in my app (or a sample app), setting effect to nil does not remove the glass appearance. Is this expected? Is the video out of date? Or is this a bug?
When a UIPageViewController is pushed in a UINavigationController, the leading swipe action from middle of screen dismisses the PageViewController instead of going to previous page.
When the Example code is opened from Xcode 16.4.0,
✅ Left Swipe action from left screen edge of screen dismisses the Page View Controller.
✅ Left Swipe action from middle of screen goes to previous Page in Page View Controller
When the Example code is opened from Xcode 26.0 - Beta 6,
✅ Left Swipe action from left screen edge of screen dismisses the Page View Controller.
❌ Left Swipe action from middle of screen sometimes goes to previous page and sometimes dismisses the Page View Controller.
Example code that the issue occurs:
import Foundation
import UIKit
import PlaygroundSupport
PlaygroundPage.current.setLiveView(
UINavigationController(rootViewController: RootViewController())
)
class RootViewController: UIViewController {
lazy var pageVCButton: UIButton = {
let button = UIButton()
button.setTitle("Open Page VC", for: .normal)
button.setTitleColor(.label, for: .normal)
button.addAction(UIAction(handler: { [weak self] _ in
self?.didTapPageVCButton()
}), for: .touchUpInside)
return button
}()
lazy var pageContainerViewController = PageContainerViewController(startIndex: 3)
func didTapPageVCButton() {
print("didTapPageVCButton")
navigationController?.pushViewController(pageContainerViewController, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
addOpenPageVCButton()
}
private func addOpenPageVCButton() {
view.addSubview(pageVCButton)
pageVCButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
pageVCButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
pageVCButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
}
class PageContainerViewController: UIViewController {
lazy var pageViewController: UIPageViewController = {
let pageViewController = UIPageViewController(
transitionStyle: .scroll,
navigationOrientation: .horizontal,
options: nil
)
pageViewController.dataSource = self
pageViewController.delegate = self
return pageViewController
}()
lazy var pages: [ColouredViewController] = [
ColouredViewController(backgroundColor: .red),
ColouredViewController(backgroundColor: .blue),
ColouredViewController(backgroundColor: .green),
ColouredViewController(backgroundColor: .yellow),
ColouredViewController(backgroundColor: .brown),
ColouredViewController(backgroundColor: .link),
ColouredViewController(backgroundColor: .cyan),
]
var startIndex = 0
init(startIndex: Int) {
super.init(nibName: nil, bundle: nil)
self.startIndex = startIndex
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.title = "Page View Controller"
print(pageViewController.gestureRecognizers)
setupPageViewController()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
private func setupPageViewController() {
addChild(pageViewController)
view.addSubview(pageViewController.view)
pageViewController.didMove(toParent: self)
pageViewController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
pageViewController.view.topAnchor.constraint(equalTo: view.topAnchor),
pageViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
pageViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
pageViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])
pageViewController.setViewControllers([pages[startIndex]], direction: .forward, animated: true)
}
}
extension PageContainerViewController: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
print("Leading Swipe")
guard let viewController = viewController as? ColouredViewController else { return nil }
guard let currentPageIndex = pages.firstIndex(of: viewController) else { return nil }
if currentPageIndex == 0 { return nil }
return pages[currentPageIndex - 1]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
print("Trailing Swipe")
guard let viewController = viewController as? ColouredViewController else { return nil }
guard let currentPageIndex = pages.firstIndex(of: viewController) else { return nil }
if currentPageIndex == pages.count - 1 { return nil }
return pages[currentPageIndex + 1]
}
}
extension PageContainerViewController: UIPageViewControllerDelegate {}
class ColouredViewController: UIViewController {
var backgroundColor: UIColor?
init(backgroundColor: UIColor) {
super.init(nibName: nil, bundle: nil)
self.backgroundColor = backgroundColor
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = backgroundColor
}
}
The isEnabled property on UITabBarItem no longer disables tab bar items as expected in iOS 18. 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