Anyone else get these warnings when using UI Tab Bar in visionOS? Are these detrimental to pushing my visionOS app to the App Review Team?
import SwiftUI
import UIKit
struct HomeScreenWrapper: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UITabBarController {
let tabBarController = UITabBarController()
// Home View Controller
let homeVC = UIHostingController(rootView: HomeScreen())
homeVC.tabBarItem = UITabBarItem(title: "Home", image: UIImage(systemName: "house"), tag: 0)
// Brands View Controller
let brandsVC = UIHostingController(rootView: BrandSelection())
brandsVC.tabBarItem = UITabBarItem(title: "Brands", image: UIImage(systemName: "bag"), tag: 1)
tabBarController.viewControllers = [homeVC, brandsVC]
return tabBarController
}
func updateUIViewController(_ uiViewController: UITabBarController, context: Context) {
// Update the UI if needed
}
}
struct HomeScreenWrapper_Previews: PreviewProvider {
static var previews: some View {
HomeScreenWrapper()
}
}
UIKit
RSS for tagConstruct and manage graphical, event-driven user interfaces for iOS or tvOS apps using UIKit.
Posts under UIKit tag
200 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
Starting iOS 16 seeing some crashes related to pdf printing in the crash reporter. It looks like the issue is not so frequent. Also, I'm unable to reproduce the crash. Looks like the app crashes when the print preview dialog is opening. According to crash reports, there are some crashes on different iOS 16 versions: 16.0.0, 16.0.2, and 16.0.3.
Printing code:
let printInfo = UIPrintInfo.printInfo()
printInfo.jobName = "Printing Job Name"
self.printViewController = UIPrintInteractionController.shared
self.printViewController?.printInfo = printInfo
self.printViewController?.printingItem = pdfURL
self.printViewController?.present(from: barButtonItem, animated: true) { (controller, completed, error) in
self.printViewController = nil
}
Stack trace:
compare_key + 4 (CGPDFObject.c:134)
bsearch + 68 (bsearch.c:70)
CGPDFDictionaryGetUnresolvedObject + 68 (CGPDFDictionary.c:153)
CGPDFDictionaryGetObject + 44 (CGPDFDictionary.c:172)
CGPDFDictionaryGetDictionary + 44 (CGPDFDictionary.c:284)
get_pages_dictionary + 68 (pdf-reader.c:410)
pdf_reader_get_number_of_pages + 76 (pdf-reader.c:557)
-[UIPrintPreviewPageFetcher fetchNumberOfItems] + 76 (UIPrintPreviewPageFetcher.m:115)
-[UIPrintPreviewViewController collectionView:numberOfItemsInSection:] + 32 (UIPrintPreviewViewController.m:482)
-[UICollectionViewData _updateItemCounts] + 220 (UICollectionViewData.mm:335)
-[UICollectionViewData isIndexPathValid:validateItemCounts:] + 52 (UICollectionViewData.mm:348)
-[UICollectionViewData validatedGlobalIndexForItemAtIndexPath:] + 36 (UICollectionViewData.mm:778)
-[UICollectionView _cellForItemAtIndexPath:] + 108 (UICollectionView.m:7112)
-[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:animator:collectionViewAnimator:] + 1384 (UICollectionView.m:9357)
-[UICollectionView _updateRowsAtIndexPaths:updateAction:updates:] + 396 (UICollectionView.m:9104)
-[UICollectionView reloadItemsAtIndexPaths:] + 52 (UICollectionView.m:9124)
-[UIPrintPreviewViewController reloadVisibleItems:] + 256 (UIPrintPreviewViewController.m:568)
__63-[UIPrintPreviewViewController updatePdfURL:options:completed:]_block_invoke_2 + 44 (UIPrintPreviewViewController.m:305)
__NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 24 (NSOperation.m:1545)
-[NSBlockOperation main] + 104 (NSOperation.m:1564)
__NSOPERATION_IS_INVOKING_MAIN__ + 16 (NSOperation.m:2189)
-[NSOperation start] + 708 (NSOperation.m:2206)
There is full stack trace I got from the Organizer
Thanks!
Overview
I've found inconsistency on view lifecycle events between UIKit and SwiftUI as the following shows when using UIVPageViewController and UIHostingController as one of its pages.
SwiftUI View
onAppear is only called at the first time to display and never called in the other cases.
UIViewController
viewDidAppear is not called at the first time to display, but it's called when the page view controller changes its page displayed.
The whole view structure is as follows:
UIViewController (root)
UIPageViewController (as its container view)
UIHostingController (as its page)
SwiftUI View (as its content view)
UIViewControllerRepresentable (as a part of its body)
UIViewController (as its content)
Environment
Xcode Version 15.4 (15F31d)
iPhone 15 Pro (iOS 17.5) (Simulator)
iPhone 8 (iOS 15.0) (Simulator)
Sample code
import UIKit
import SwiftUI
class ViewController: UIViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
private var pageViewController: UIPageViewController!
private var viewControllers: [UIViewController] = []
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
private func setup() {
pageViewController.delegate = self
pageViewController.dataSource = self
let page1 = UIHostingController(rootView: MainPageView())
let page2 = UIViewController()
page2.view.backgroundColor = .systemBlue
let page3 = UIViewController()
page3.view.backgroundColor = .systemGreen
viewControllers = [page1, page2, page3]
pageViewController.setViewControllers([page1], direction: .forward, animated: false)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
guard let pageViewController = segue.destination as? UIPageViewController else { return }
self.pageViewController = pageViewController
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
print("debug: \(#function)")
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
print("debug: \(#function)")
guard let viewControllerIndex = viewControllers.firstIndex(of: viewController) else { return nil }
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0, viewControllers.count > previousIndex else { return nil }
return viewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
print("debug: \(#function)")
guard let viewControllerIndex = viewControllers.firstIndex(of: viewController) else { return nil }
let nextIndex = viewControllerIndex + 1
guard viewControllers.count != nextIndex, viewControllers.count > nextIndex else { return nil }
return viewControllers[nextIndex]
}
}
struct MainPageView: View {
var body: some View {
VStack(spacing: 0) {
PageContentView()
PageFooterView()
}
.onAppear { print("debug: \(type(of: Self.self)) onAppear") }
.onDisappear { print("debug: \(type(of: Self.self)) onDisappear") }
}
}
struct PageFooterView: View {
var body: some View {
Text("PageFooterView")
.padding()
.frame(maxWidth: .infinity)
.background(Color.blue)
.onAppear { print("debug: \(type(of: Self.self)) onAppear") }
.onDisappear { print("debug: \(type(of: Self.self)) onDisappear") }
}
}
struct PageContentView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> some UIViewController {
PageContentViewController()
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
}
class PageContentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
private func setup() {
view.backgroundColor = .systemYellow
let label = UILabel()
label.text = "PageContentViewController"
label.font = .preferredFont(forTextStyle: .title1)
view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("debug: \(type(of: Self.self)) \(#function)")
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
print("debug: \(type(of: Self.self)) \(#function)")
}
}
Logs
// Display the views
debug: MainPageView.Type onAppear
debug: PageFooterView.Type onAppear
// Swipe to the next page
debug: pageViewController(_:viewControllerAfter:)
debug: pageViewController(_:viewControllerBefore:)
debug: PageContentViewController.Type viewDidDisappear(_:)
debug: pageViewController(_:didFinishAnimating:previousViewControllers:transitionCompleted:)
debug: pageViewController(_:viewControllerAfter:)
// Swipe to the previous page
debug: PageContentViewController.Type viewDidAppear(_:)
debug: pageViewController(_:didFinishAnimating:previousViewControllers:transitionCompleted:)
debug: pageViewController(_:viewControllerBefore:)
As you can see here, onAppear is only called at the first time to display but never called in the other cases while viewDidAppear is the other way around.
iOS 18 22A5282m
navigationController.viewControllers = [UIViewController()]
or
navigationController.setViewControllers([UIViewController()], animated: false)
doesn't change navigation stack anymore.
If I do something like this:
var viewControllers = navigationController.viewControllers
if let lastViewController = viewControllers.popLast() {
navigationController.viewControllers = viewControllers
navigationController.pushViewController(lastViewController, animated: false)
}
}
I got crash: pushing the same view controller instance more than once
If I set delay:
var viewControllers = navigationController.viewControllers
if let lastViewController = viewControllers.popLast() {
navigationController.viewControllers = viewControllers
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
navigationController.pushViewController(lastViewController, animated: false)
}
}
it will work but with unnecessary transitions.
Should it work like this in iOS 18 ?
We have a UITabBarController in our iPhone App which has 5 tabs with UITableViewControllers (constructed from the storyboard). Before iOS18 beta 1 (and 2) this was working fine without any problems (objective-C).
Since iOS18 beta 1 (and beta 2 still has this problem) a strange render glitch occurs when activating a tab from the tab bar at the bottom.
As soon as a tab is activated (by tapping on the icon at the bottom) the tab with the UITableViewController becomes visible and draws its content starting at the very top of the screen (pos 0,0) right through/over the Navigation bar which at that point is showing a title and a rightBarButtonItem.
The tab with the UITableViewController seems not aware there is a navigation bar visible.
Then after ~0.3 seconds the tab with the UITableViewContoller is automatically rendered again or moved down and now its content starts below the UINavigationBar as expected, this is 100% reproducible and occurs on every activation of a tab in the UITabBarController.
Is anyone else also getting this behavior in their App since iOS18?
I'm aware that UITabBarController is being renewed but I can't find any information on why this behavior might occur. I was hoping beta 2 would solve the problem but it doesn't.
Constructing the UITabBarController in the code with the new UITab objects (instead of constructing them from the storyboard) also shows this problem.
Hey everyone,
I’m facing a bug when using UIVisualEffectView in a SwiftUI context via UIViewRepresentable. When the SwiftUI view modifier .blur(radius: xx, opaque: true) is applied to it during rotation, the blur effect isn’t applied. Instead, the view becomes completely white or black, depending on the UIBlurEffect.Style applied to the UIVisualEffectView.
I’m not sure how to proceed with this issue and am seeking your help. Below is a simple, reproducible piece of code:
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Circle()
.fill(.pink)
GlassBackgroundView()
.blur(radius: 7, opaque: true)
}
.ignoresSafeArea()
}
}
#Preview {
ContentView()
}
private struct GlassBackgroundView: UIViewRepresentable {
func makeUIView(context: Context) -> UIVisualEffectView {
UIVisualEffectView(effect: UIBlurEffect(style: .regular))
}
func updateUIView(_ uiView: UIVisualEffectView, context: Context) { }
}
In testing my app with the WWDC24 iOS 18 beta, I have noticed that most of the menu items in the navigation bar and title menu are either missing, disabled, or nonfunctional.
The structure of my app's UI is a UIDocumentController subclass as the root view controller of a UINavigationController.
In debugging the problem with title menu items, I noticed that the responder chain from the UICommand.sender now starts at the UINavigationBar and goes up from there, without passing through the UIDocumentViewController itself. Now, only the actions I've defined in the AppDelegate are accessible.
I'm not exactly sure how this was organized on iOS 17, but the responder chain did include the UIDocumentViewController, where I have implemented most of the menu item actions.
This seems like a UIKit bug, but I am investigating possible workarounds in case Apple does not fix it. Suggestions welcome.
In the past, it seemed that if you used the app switcher and killed the app, that state restoration data would not persist, and starting the app again would not load any stored state data. But now (at lest in iOS 17) that is no longer the case.
There are situations where the old state might cause issues, which we obviously need to fix, but users should be able to clear the state data.
I am not using sessions and am using the older methods - application:shouldSaveApplicationState: and application:shouldRestoreApplicationState:.
My question is, how can I tell my users to reset/clear state restoration data if needed?
Hi,
I'm trying to investigate if there is any way to have an app that displays an automaker app when the Carplay environment has the automaker protocol string, and displays a Carplay App (Driving Task) when there isn't the automaker protocol string.
I was able to start developing an automaker app, but with an iOS16.0 deprecated method (with UIScreen Notifications), I'm not able to do it via the scene delegate... There is any kind of documentation of how to do it?
I think the clue may be in the scene delegate with the property Preferred Default Scene Session Role, where I think the automaker app is a Window Application Session Role, but the scene delegate is not triggered when I open the Carplay App in the simulator.
So am I missing something? Is there a way to do it or have I to publish two apps in order to use the two kind of carplay apps... ?
Thank you very much.
macOS 15 introduces SwiftUI.WindowLevel for macOS which allows setting the level of windows.
Is there an equivalent new API for UIKit.UIWindowScene on Mac Catalyst? I haven't been able to find it.
On iOS18 beta1 & beta2 builds, calling
collectionView.cancelInteractiveTransition()
after a call to
startInteractiveTransition(to:completion:)
seems to remove the intermediate transition layout object from the collection view, but doesn't reinstalls the original layout, which results in the collection view disappearing completely.
This was not the case in previous iOS versions, so maybe a beta bug?
Possibly related, Xcode logs the following error in the console in the exact moment when the collectionView disappears:
“Requesting visual style in an implementation that has disabled it, returning nil. Behavior of caller is undefined.”
I filled a bug report, together with sample project, just in case FB14057335
Thanks!
Whenever I make a new app I end up getting bug reports from people who say they can't see text, or they can just about see white text against an almost white background. It always turns out to be because their phone is in dark mode, which I don't use.
Almost all of my views have the background color set to "System background color" which is the default value when you create a view. Why does dark mode change the color of the text but not the background?
Currently, we are implementing an undo/redo feature in UITextView.
However, we cannot use the built-in UndoManager in UITextView because we have multiple UITextView instances inside a UICollectionView.
Since UICollectionView recycles UITextView instances, the same UITextView might be reused in different rows, making the built-in UndoManager unreliable.
The shouldChangeTextIn method in UITextViewDelegate is key to implementing undo/redo functionality properly. Here is an example of our implementation:
extension ChecklistCell: UITextViewDelegate {
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
// Get the current text
let s = textView.text ?? ""
// Get the starting position of the change
let start = range.location
// Get the number of characters that will be replaced
let count = range.length
// Get the number of characters that will be added
let after = text.count
print(">>>> The current text = \"\(s)\"")
print(">>>> The starting position of the change = \(start)")
print(">>>> The number of characters that will be replaced = \(count)")
print(">>>> The number of characters that will be added = \(after)")
print(">>>>")
if let delegate = delegate, let checklistId = checklistId, let index = delegate.checklistIdToIndex(checklistId) {
delegate.attachTextAction(s: s, start: start, count: count, after: after, index: index)
}
return true
}
}
Working scene behind the UITextViewDelegate
However, this implementation does not work well with non-English input using an IME. When using an IME, there is an intermediate input before the final input is produced. For example, typing "wo" (intermediate input) produces "我" (final input). Currently, UITextViewDelegate captures both "wo" and "我".
UITextViewDelegate captures both "wo" and "我"
Is there a way to ignore the intermediate input from IME and only consider the final input?
In Android, we use the beforeTextChanged method in TextWatcher to seamlessly ignore the intermediate input from IME and only consider the final input. You can see this in action in this
Android captures only "我"
Is there an equivalent way in iOS to ignore the intermediate input from IME and only take the final input into consideration?
I am currently refactoring my app's side menu to be more like Twitter's. I have the UI down in terms of how the side menu looks and appears, but the issue is navigating to a view from the side menu. The views that a user can go to from the side menu are a mix of SwiftUI views & UIKit View Controllers. As of right now, when a user navigates to a view from the side menu, it presents it modally as a sheet. I want it to have regular navigation, where the user goes to the view displayed in full screen and can tap on the back button to go back to the previous view.
Here is the associated code:
SideMenuView.swift
SideMenuViewModel.swift
How can I modify the navigation logic to be like Twitter's? I've been stuck for days trying to find a fix but it has been a struggle.
I have a view in storyboard, with a tableView and a coloured UIView.
The UIView is declared after tableView, so it appears on top of tableView.
However, it appears semi transparent over the tableView.
In addition, I cannot set its alpha channel to values other than 1 or 0 (e.g., 0.9)
But if I create the view programmatically, the view if fully opaque as expected.
What am I missing ?
Hello,
I try to do the same as the UIListContentConfiguration. Because I want to have a UITableViewCell with an image at beginning, then 2 Labels and an image at the end (accessoryView/Type should also be usable). I also want to have the dynamic behavior of the two labels, that if one or both of them exceeds a limit, that they are put under each under. But I cannot use the UIListContentConfiguration.valueCell, because of the extra image at the end.
So I have tried to make a TextWrapper as an UIView, which contains only the two UILabels and the TextWrapper should take care of the dynamic height of the UILabels and put them side to side or under each other.
But here in this post Im only concentrating on the issue with the labels under each other, because I have managed it to get it working, that I have two sets of Constraints and switch the activeStatus of the constraints, depending on the size of the two labels. But currently only the thing with the labels under each under makes problems.
Following approaches I have tried for the TextWrapper:
Using only constraints in this Wrapper (results in ambiguous constraints)
Used combinations of constraints + intrinsicContentSize (failed because it seems that invalidateIntrinsicContentSize doesn't work for me)
Approach with constraints only
Following code snippet results in ambiguous vertical position and height. Because I have 3 constraints (those with the defaultHigh priorities).
class TextWrapper: UIView {
let textLabel = UILabel()
let detailLabel = UILabel()
init() {
super.init(frame: .zero)
self.addSubview(self.textLabel)
self.addSubview(self.detailLabel)
self.textLabel.numberOfLines = 0
self.detailLabel.numberOfLines = 0
self.directionalLayoutMargins = .init(top: 8, leading: 16, bottom: 8, trailing: 8)
self.translatesAutoresizingMaskIntoConstraints = false
self.textLabel.translatesAutoresizingMaskIntoConstraints = false
self.detailLabel.translatesAutoresizingMaskIntoConstraints = false
// Content Size
self.textLabel.setContentCompressionResistancePriority(.required, for: .vertical)
self.detailLabel.setContentCompressionResistancePriority(.required, for: .vertical)
// Constraints
self.textLabel.leadingAnchor.constraint(equalTo: self.layoutMarginsGuide.leadingAnchor).isActive = true
self.textLabel.topAnchor.constraint(equalTo: self.layoutMarginsGuide.topAnchor).constraint(with: .defaultHigh)
self.textLabel.widthAnchor.constraint(lessThanOrEqualTo: self.layoutMarginsGuide.widthAnchor).isActive = true
self.detailLabel.leadingAnchor.constraint(equalTo: self.layoutMarginsGuide.leadingAnchor).isActive = true
self.detailLabel.topAnchor.constraint(equalTo: self.textLabel.bottomAnchor, constant: 2).constraint(with: .defaultHigh)
self.detailLabel.bottomAnchor.constraint(equalTo: self.layoutMarginsGuide.bottomAnchor).constraint(with: .defaultHigh)
self.detailLabel.widthAnchor.constraint(lessThanOrEqualTo: self.layoutMarginsGuide.widthAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Approach with intrinsicContentSize
Pretty similar to the above, with only difference that I invalidate the intrinsicContentSize in the layoutSubviews, because at the first call of the intrinsicContentSize the width of the View is zero. I also tried different things like setNeedsLayout with layoutIfNeeded but nothing really works. After I invalidate the intrinsicContentSize in the layoutSubviews the intrinsicContentSize is called with the correct width of the View and calculates the correct height, but the TableView doesn't update the height accordingly.
class TextWrapper: UIView {
let textLabel = UILabel()
let detailLabel = UILabel()
init() {
super.init(frame: .zero)
self.addSubview(self.textLabel)
self.addSubview(self.detailLabel)
self.textLabel.numberOfLines = 0
self.detailLabel.numberOfLines = 0
self.directionalLayoutMargins = .init(top: 8, leading: 16, bottom: 8, trailing: 8)
self.translatesAutoresizingMaskIntoConstraints = false
self.textLabel.translatesAutoresizingMaskIntoConstraints = false
self.detailLabel.translatesAutoresizingMaskIntoConstraints = false
// Content Size
self.textLabel.setContentCompressionResistancePriority(.required, for: .vertical)
self.detailLabel.setContentCompressionResistancePriority(.required, for: .vertical)
// Constraints
self.textLabel.leadingAnchor.constraint(equalTo: self.layoutMarginsGuide.leadingAnchor).isActive = true
self.textLabel.topAnchor.constraint(equalTo: self.layoutMarginsGuide.topAnchor).constraint(with: .defaultHigh)
self.textLabel.widthAnchor.constraint(lessThanOrEqualTo: self.layoutMarginsGuide.widthAnchor).isActive = true
self.detailLabel.leadingAnchor.constraint(equalTo: self.layoutMarginsGuide.leadingAnchor).isActive = true
self.detailLabel.topAnchor.constraint(equalTo: self.textLabel.bottomAnchor, constant: 2).constraint(with: .defaultHigh)
self.detailLabel.widthAnchor.constraint(lessThanOrEqualTo: self.layoutMarginsGuide.widthAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var intrinsicContentSize: CGSize {
let maxLabelWidth = self.bounds.width
guard maxLabelWidth > 0 else {
// The first time it has a width of 0, so we are giving a default height in this case, to dont produce a error in TableView
return CGSize(width: UIView.noIntrinsicMetric, height: 44)
}
let textLabelSize = self.textLabel.sizeThatFits(CGSize(width: maxLabelWidth, height: .greatestFiniteMagnitude))
let detailLabelSize = self.detailLabel.sizeThatFits(CGSize(width: maxLabelWidth, height: .greatestFiniteMagnitude))
let totalHeight = textLabelSize.height + detailLabelSize.height + 16
return CGSize(width: UIView.noIntrinsicMetric, height: totalHeight)
}
override func layoutSubviews() {
super.layoutSubviews()
self.invalidateIntrinsicContentSize()
}
I also tried to use the intrinsicContentSize with only layoutSubviews, but this haven't worked also.
Does anybody have ran into such issues? And know how to fix that?
We have an app with a UITabBarController.
On visionOS it is automatically shown as an ornament (as expected).
However, we would like to temporarily hide it in some situations.
Is it possible to hide/show the ornament representation of the UITabBarController's tab bar?
We encountered a deadlock scenario when our app runs on iOS 18. It happens when we use the async version of UICollectionViewDiffableDataSource.apply() and request a section snapshot from within the UICollectionViewCompositionalLayoutSectionProvider.
The deadlock doesn't happen when using the completion handler version of UICollectionViewDiffableDataSource.apply() or when requesting a full snapshot from the data source.
On iOS 17 there is no issue.
import UIKit
class ViewController: UICollectionViewController {
private enum SectionIdentifier: Hashable {
case test
}
private enum ItemIdentifier: Hashable {
case test(UUID)
}
private typealias Snapshot = NSDiffableDataSourceSnapshot<SectionIdentifier, ItemIdentifier>
private typealias SectionSnapshot = NSDiffableDataSourceSectionSnapshot<ItemIdentifier>
private typealias DataSource = UICollectionViewDiffableDataSource<SectionIdentifier, ItemIdentifier>
private lazy var dataSource: DataSource = {
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewCell, UIColor> { cell, _, color in
cell.contentView.backgroundColor = color
}
return DataSource(collectionView: collectionView) { [weak self] collectionView, indexPath, itemIdentifier in
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: .red)
}
}()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = dataSource
collectionView.collectionViewLayout = UICollectionViewCompositionalLayout { [weak self] section, environment -> NSCollectionLayoutSection? in
// calling `UICollectionViewDiffableDataSource.snapshot(for:)` from the `UICollectionViewCompositionalLayoutSectionProvider` leads to deadlock on iOS 18
let numberOfItems = self?.dataSource.snapshot(for: .test).items.count ?? 0 // deadlock
// calling `UICollectionViewDiffableDataSource.snapshot()` causes no issue
// let numberOfItems = self?.dataSource.snapshot().numberOfItems(inSection: .test) ?? 0 // works
let item = NSCollectionLayoutItem(layoutSize: .init(widthDimension: .absolute(100), heightDimension: .absolute(100)))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .absolute(100)), repeatingSubitem: item, count: numberOfItems)
group.interItemSpacing = .fixed(5)
return .init(group: group)
}
var snapshot = Snapshot()
snapshot.appendSections([.test])
snapshot.appendItems([.test(UUID()), .test(UUID()), .test(UUID()), .test(UUID())])
// using the async wrapper `UICollectionViewDiffableDataSource.apply()` on any thread leads to deadlock on iOS 18
Task { await dataSource.apply(snapshot) } // deadlock
// Task { @MainActor in await dataSource.apply(snapshot) } // deadlock
// using `UICollectionViewDiffableDataSource.apply()` with completion handling causes no issue
// Task { dataSource.apply(snapshot) } // works
// dataSource.apply(snapshot) // works
}
}
Full example project at https://github.com/antiraum/iOS18_UICollectionViewDiffableDataSource_deadlock
Prior to macOS 14, a Catalyst app could present a UIAlertController as a popover, just as you can on the iPad. While this still works on the iPad, the presentation on macOS now uses the same style as the iPhone.
The UIAlertController's popoverPresentationController property is always nil, no matter how it is configured.
Doe anyone know of a way to restore the prior behavior under Sonoma?