FB14569448
Since iOS 18 beta came out, our app has been failing at some point when a view controller is dismissed and it appears the main thread ends up looping in layout logic and logs the following to the console. The app hangs until it's terminated due to a memory overallocation issue.
Anyone else seen this?
<_UINavigationBarItemStackEntry: 0x302ac73f0> normalLayout[active]=0x1391ad880 searchLayout=0x0 item=<UINavigationItem: 0x13913a080> style=navigator: Ignoring unexpected nonmatching counterpart: (null)
This is an internal UIKit bug.
Snapshotting a view (0x1391e7c00, _UIButtonBarStackView) that has not been rendered at least once requires afterScreenUpdates:YES.
frame #0: 0x000000019bb07000 libobjc.A.dylibobjc_msgSend frame #1: 0x00000001a0fb4224 UIKitCore-[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededCollectingViews:forSecondPass:] + 144
frame #2: 0x00000001c270a030 CoreAutoLayout-[NSISEngine withBehaviors:performModifications:] + 84 frame #3: 0x00000001a0fe5424 UIKitCore__100-[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededWithViewForVariableChangeNotifications:]_block_invoke + 120
frame #4: 0x00000001a0fe3ae0 UIKitCore-[UIView(AdditionalLayoutSupport) _withUnsatisfiableConstraintsLoggingSuspendedIfEngineDelegateExists:] + 112 frame #5: 0x00000001a0fe2b4c UIKitCore-[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededWithViewForVariableChangeNotifications:] + 172
frame #6: 0x00000001a0fa9e90 UIKitCore-[UIView _updateConstraintsAtEngineLevelIfNeededWithViewForVariableChangeNotifications:] + 400 frame #7: 0x00000001a0fa9a18 UIKitCore-[UIView _updateConstraintsAsNecessaryAndApplyLayoutFromEngine] + 312
frame #8: 0x00000001a0fa97a4 UIKitCore-[UIView(Hierarchy) layoutSubviews] + 204 frame #9: 0x00000001a124fa78 UIKitCore-[_UIButtonBarStackView layoutSubviews] + 56
frame #10: 0x00000001a0f6430c UIKitCore-[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2492 frame #11: 0x00000001a029d8d0 QuartzCoreCA::Layer::layout_if_needed(CA::Transaction*) + 496
frame #12: 0x00000001a0fa81d0 UIKitCore-[UIView(Hierarchy) layoutBelowIfNeeded] + 300 frame #13: 0x00000001a1843fa4 UIKitCore-[_UINavigationBarTransitionContextCrossfade _prepareContentView] + 132
frame #14: 0x00000001a18443bc UIKitCore-[_UINavigationBarTransitionContextCrossfade prepare] + 84 frame #15: 0x00000001a184e59c UIKitCore-[_UINavigationBarVisualProviderModernIOS _performAnimationWithTransitionCompletion:transition:] + 1524
frame #16: 0x00000001a180f938 UIKitCore-[UINavigationBar _popNavigationItemWithTransitionAssistant:] + 220 frame #17: 0x00000001a180f5f8 UIKitCore-[UINavigationBar _popNavigationItemWithTransition:] + 224
frame #18: 0x00000001a13bec9c UIKitCore-[UINavigationBar _setItemsUpToItem:transition:] + 240 frame #19: 0x00000001a13be76c UIKitCore-[UIViewController _removeNavigationItemsFromNavigationController:transition:] + 232
frame #20: 0x00000001a19c9dc0 UIKitCore__89-[UINavigationController _immediatelyApplyViewControllers:transition:animated:operation:]_block_invoke_3 + 800 frame #21: 0x00000001a19d0b2c UIKitCore__98-[UINavigationController _shouldSkipHostedRefreshControlUpdateSchedulingDeferredUpdateIfNecessary]_block_invoke + 40
frame #22: 0x00000001a19d7574 UIKitCore-[UINavigationController transitionConductor:didStartDeferredTransition:context:] + 792 frame #23: 0x00000001a21c9600 UIKitCore-[_UIViewControllerTransitionConductor startDeferredTransitionIfNeeded] + 688
frame #24: 0x00000001a10ab040 UIKitCore-[UINavigationController __viewWillLayoutSubviews] + 84 frame #25: 0x00000001a1350bcc UIKitCore-[UILayoutContainerView layoutSubviews] + 172
frame #26: 0x00000001a0f6430c UIKitCore-[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2492 frame #27: 0x00000001a029d8d0 QuartzCoreCA::Layer::layout_if_needed(CA::Transaction*) + 496
frame #28: 0x00000001a029d45c QuartzCoreCA::Layer::layout_and_display_if_needed(CA::Transaction*) + 148 frame #29: 0x00000001a02f64e0 QuartzCoreCA::Context::commit_transaction(CA::Transaction*, double, double*) + 472
frame #30: 0x00000001a02736c4 QuartzCoreCA::Transaction::commit() + 648 frame #31: 0x00000001a02b6584 QuartzCoreCA::Transaction::flush_as_runloop_observer(bool) + 88
frame #32: 0x00000001a1002c3c UIKitCore_UIApplicationFlushCATransaction + 52 frame #33: 0x00000001a10002dc UIKitCore_UIUpdateSequenceRun + 84
frame #34: 0x00000001a0ffff2c UIKitCoreschedulerStepScheduledMainSection + 172 frame #35: 0x00000001a1000df0 UIKitCorerunloopSourceCallback + 92
frame #36: 0x000000019e7edf90 CoreFoundation__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28 frame #37: 0x000000019e7edf24 CoreFoundation__CFRunLoopDoSource0 + 176
frame #38: 0x000000019e7eba10 CoreFoundation__CFRunLoopDoSources0 + 244 frame #39: 0x000000019e7eac14 CoreFoundation__CFRunLoopRun + 840
frame #40: 0x000000019e7ea4c8 CoreFoundationCFRunLoopRunSpecific + 572 frame #41: 0x00000001eb06d1c4 GraphicsServicesGSEventRunModal + 164
frame #42: 0x00000001a1334a90 UIKitCore-[UIApplication _run] + 816 frame #43: 0x00000001a13e2d1c UIKitCoreUIApplicationMain + 340
frame #44: 0x000000010038ca7c LabWare Developmentmain at AppDelegate.swift:31:7 frame #45: 0x00000001c4f4e9b4 dyldstart + 2724
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
The following:
tabBarController.tabs = [
UITab(title: "First",
image: UIImage(...),
identifier: "First Tab") { _ in
myViewController()
}
Works as expected. Tap on a a tab or sidebar item, and myViewController appears.
However, the following:
let collectionsGroup = UITabGroup(
title: "Lists",
image: nil,
identifier: "Tabs.CollectionsGroup",
children: [
UITab(title: "First", image: nil, identifier: "First Tab") { _ in
myViewController()
}
]) { _ in
fallbackViewController()
}
Does not work as expected. Tapping on any tab in the sidebar always displays fallbackViewController, instead of myViewController or any other specified view controllers in the given tab.
Am I doing something wrong, or is this a bug with the current betas?
Topic:
UI Frameworks
SubTopic:
UIKit
The following is a UIKit app that displays a collection view with list layout and diffable data source (one section, one row).
class ViewController: UIViewController {
var collectionView: UICollectionView!
var dataSource: UICollectionViewDiffableDataSource<String, String>!
override func viewDidLoad() {
super.viewDidLoad()
configureHierarchy()
configureDataSource()
}
func configureHierarchy() {
collectionView = .init(frame: .zero, collectionViewLayout: createLayout())
view.addSubview(collectionView)
collectionView.frame = view.bounds
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
func createLayout() -> UICollectionViewLayout {
UICollectionViewCompositionalLayout { section, layoutEnvironment in
let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
return NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment)
}
}
func configureDataSource() {
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { cell, indexPath, itemIdentifier in
var backgroundConfiguration = UIBackgroundConfiguration.listGroupedCell()
backgroundConfiguration.backgroundColor = .systemBlue
cell.backgroundConfiguration = backgroundConfiguration
}
dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier)
}
var snapshot = NSDiffableDataSourceSnapshot<String, String>()
snapshot.appendSections(["main"])
snapshot.appendItems(["demo"])
dataSource.apply(snapshot, animatingDifferences: false)
}
}
If you tap on the row, it seems like selection doesn't happen: giving the cell a blue background broke its default background color transformer.
Here's what I've tried and didn't work:
Setting the collection view's delegate and specifying that you can select any row
Setting the color transformer to .grayscale
Setting the backgroundConfiguration to UIBackgroundConfiguration.listGroupedCell().updated(for: cell.configurationState)
Combinations of the approaches above
Setting the color transformer to UIBackgroundConfiguration.listGroupedCell().backgroundColorTransformer and cell.backgroundConfiguration?.backgroundColorTransformer (they're both nil)
Setting the cell's backgroundColor directly
I also considered using a custom color transformer:
var backgroundConfiguration = UIBackgroundConfiguration.listGroupedCell()
backgroundConfiguration.backgroundColorTransformer = UIConfigurationColorTransformer { _ in
if cell.configurationState.isSelected || cell.configurationState.isHighlighted {
.systemBlue.withAlphaComponent(0.7)
} else {
.systemBlue
}
}
cell.backgroundConfiguration = backgroundConfiguration
However, if you push a view controller when you select the row, the row gets deselected, which is unfortunate, giving that I like to deselect rows in viewWillAppear(_:) to keep users more oriented.
There might be ways to circumvent this, but my custom color transformer might still differ from the default one in some other ways.
So how do I assign the default one to my cell?
Topic:
UI Frameworks
SubTopic:
UIKit
In iOS 18.0, UICollectionView/scrollToItem(at:at:animated:) causes UICollectionView to scroll with an awkward stuttered animation at about 4 frames per second on the device.
This behavior is not observed in iOS 15, 16, or 17.
I submitted FB13986989 about this issue 6 weeks ago when I first observed it in iOS 18 beta 1, but I have not received a response.
The issue has not been fixed in iOS 18.0 beta 2, 3, or 4.
Is this a known issue? Does Apple intend to fix it?
Assuming Apple has not prioritized fixing this bug, could a UIKit engineer suggest a workaround?
My app relies on scrollTo working properly and now looks awful on iOS 18.
Thank you for any assistance you can provide.
The following code, will create a red color text, without strike-through.
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let text = "Hello World"
let textCount = text.count
let fullRange = NSRange(location: 0, length: textCount)
var attributedText = NSMutableAttributedString(string: text)
attributedText.addAttribute(.foregroundColor, value: UIColor.green, range: fullRange)
attributedText.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: fullRange)
label.attributedText = attributedText
attributedText = NSMutableAttributedString(string: text)
attributedText.addAttribute(.foregroundColor, value: UIColor.red, range: fullRange)
attributedText.removeAttribute(NSAttributedString.Key.strikethroughStyle, range: fullRange)
label.attributedText = attributedText
}
}
However, if I trigger label.text in between, it will cause the following strange behavior : A red color text, with strike-through created at the end of function.
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let text = "Hello World"
let textCount = text.count
let fullRange = NSRange(location: 0, length: textCount)
var attributedText = NSMutableAttributedString(string: text)
attributedText.addAttribute(.foregroundColor, value: UIColor.green, range: fullRange)
attributedText.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: fullRange)
label.attributedText = attributedText
// Why this will cause a red color text, with strike-through created at the end of function?
label.text = text
attributedText = NSMutableAttributedString(string: text)
attributedText.addAttribute(.foregroundColor, value: UIColor.red, range: fullRange)
attributedText.removeAttribute(NSAttributedString.Key.strikethroughStyle, range: fullRange)
label.attributedText = attributedText
}
}
Does anyone what is the reason behind this behavior, and how I can avoid such? Thank you.
Topic:
UI Frameworks
SubTopic:
UIKit
To enable editing in the elevated Tab Bar or sidebar on iPadOS, we need to use UITabBar. However, using UITabBar restricts reordering in compact mode with the bottom tab bar. Instead of showing the tabs, the editing view only displays the message 'Drag the icons to organize tabs.' How can we resolve this issue?
Find demo project here
I have already searched for a lot of information about this issue. Many people have encountered it with UISplitViewController or on iPadOS, but I am experiencing this problem on iOS 18. It does occasionally occur on iOS 17, but it definitely happens on iOS 18. The error messages are as follow:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Layout requested for visible navigation bar, <UINavigationBar: 0x14a6b3c00; frame = (0 44; 375 44); autoresize = W; tintColor = UIExtendedGrayColorSpace 1 1; layer = <CALayer: 0x3026a9da0>> delegate=0x14a674600 standardAppearance=0x300d1d0a0 scrollEdgeAppearance=0x300d1cf50, when the top item belongs to a different navigation bar. topItem = <UINavigationItem: 0x14a7f8780> style=navigator leftBarButtonItems=0x302423b40, navigation bar = <UINavigationBar: 0x14adcb700; frame = (0 0; 375 44); autoresize = W; tintColor = UIExtendedGrayColorSpace 1 1; layer = <CALayer: 0x302680940>> delegate=0x14ade8c00 standardAppearance=0x300d7a7d0 scrollEdgeAppearance=0x300d7a610, possibly from a client attempt to nest wrapped navigation controllers.'
*** First throw call stack:
(0x193f3a08c 0x191242698 0x19330a8fc 0x196939290 0x1966847b4 0x1967cdc88 0x1959ba6d8 0x1966c86bc 0x196769d50 0x1967cae58 0x196771f28 0x196b0fcb8 0x196764800 0x196684280 0x1959ba6d8 0x1959ba264 0x195a12edc 0x195990560 0x1968295f0 0x193f1c218 0x193f0a4c0 0x193f09b84 0x193f09368 0x1e00fc1c4 0x196a569b0 0x196b04d54 0x10462b1fc 0x1ba144734)
libc++abi: terminating due to uncaught exception of type NSException
I am sure about the process on iOS 17, first I use UINavigationController.present(UINavigationController()) and then call keyWindow.rootViewController = UINavigationController(), it crashes. So I can dismiss the presentedViewController before assign keyWindow.rootViewController. However, on iOS 18, the app crash when launch.
I'm in the process of rewriting a TableView as a modern CollectionView, using UICollectionLayoutListConfiguration and UICollectionViewCompositionalLayout.list together with a diffable data source.
One feature of our TableView is that it has section headers that are pinned when scrolling (plain table view style). They also change their background based on whether they are currently pinned or not. This is accomplished by setting UITableViewHeaderFooterView's automaticallyUpdatesBackgroundConfiguration to false and override updateConfiguration(using:).
However, I don't see a way of doing the same for a CollectionView's header. I can easily provide a header view by setting the supplementaryViewProvider of the data source, and they are sticky thanks to UICollectionLayoutListConfiguration.Appearance.plain But the header view itself is a UICollectionReusableView which does not seem to have the same mechanism as UITableViewHeaderFooterView for reacting to changes to the pinned state.
Is it at all possible to accomplish this with CollectionView headers? If so, how?
Topic:
UI Frameworks
SubTopic:
UIKit
In iOS 18 beta 5 version, setting the conversion to NSAttributedString using [.documentType: NSAttributedString.DocumentType.html] occasionally causes crashes.
The same issue was encountered in previous versions. However, after running the code on the main thread, it no longer crashed. But after upgrading to the iOS 18 beta 5 version, it occasionally crashes even when running on the main thread.
How can this issue be resolved?
thx~
after update to iOS 18 beta5,We found that the next code will crash.
When we use html string to generate to a NSMutableAttributedString object. It will be crash.
NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithData:[@"some html string" dataUsingEncoding:NSUTF8StringEncoding] options:@{ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute : @(NSUTF8StringEncoding) } documentAttributes:nil error:nil];
Topic:
UI Frameworks
SubTopic:
UIKit
Hi everyone,
I'm trying to solve an issue with focus not being updated in UISearchController keyboard after some letters are typed (one or more if I'm lucky enough to swipe quickly).
This started happening when I switched to Xcode 15 and the latest tvOS SDK.
The focus engine logs do not explain much to me
The result of the focus update was determined from the following preferred focus search:
|
| Starting preferred focus search.
| <UIViewController: 0x10711d180>
| └ <RCTRootView: 0x10711a810>
| │ No more preferred environments. Trying to infer environment from visual layout...
| │ Found environment: <UIKeyboard: 0x105ffcc40>
| └ <UIKeyboard: 0x105ffcc40>
| (info) It's focusable!
|
Moving focus from <UIKeyboard: 0x105ffcc40> to <UIKeyboard: 0x105ffcc40> in focus system <UIFocusSystem: 0x303568600>.
Ignoring focus update request for disappearing focus environment <UIKBFloatingKeyView: 0x105eeb130>.
<<TYPE LETTER GOES HERE >>
- ISSUE: This environment does not contain the currently focused item.
Ignoring focus update request for disappearing focus environment <_UITextLayoutFragmentView: 0x105fff620>.
- ISSUE: This environment does not contain the currently focused item.
Ignoring focus update request for disappearing focus environment <UISearchBarTextFieldLabel: 0x105ff6f70>.
- ISSUE: This environment does not contain the currently focused item.
Ignoring focus update request for disappearing focus environment <UILabel: 0x105ff5090>.
- ISSUE: This environment does not contain the currently focused item.
In the "focus-freeze" state is not possible to swipe anymore, but I still can repeat typing of the last letter.
I'd appreciate any hints on how to debug this problem.
Is it possible to list gesture recognizers or other objects that are consuming TV remote events ?
I've confirmed the the [UIWindow sendEvent:] is still being triggered.
I'm experiencing an issue in my iOS app where tapping to select text in a UITextView that is embedded within a UIScrollView causes the scroll view to jump to an incorrect position. This problem seems to occur only on iOS 15. Does anyone know how to fix this issue, or is there a known bug regarding this behavior on iOS 15?
class ViewController: UIViewController, UITextViewDelegate, UIGestureRecognizerDelegate {
@IBOutlet weak var textView: UITextView!
@IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
textView.isScrollEnabled = false
textView.layoutManager.allowsNonContiguousLayout = false
}
}
so, we used iOS18 beat5 int our APP,
crash list:
try self.init(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil)
html to NSAttributedString crash
UINavigationController.viewControllers.remove(at: referIndex)
Topic:
UI Frameworks
SubTopic:
UIKit
I know, for secure reason, apple forbidden app the ability to get clipboard data in background.
But in some case, for example, i use appium to e2e testing my real device. And I could't get clipboard from webdriver. Because it's running background!
So, it's there any way to get clipboard from webdriver? It bothers me.
I have UISearchBar, when I run from xCode placeholder and icon is working well but when i run from simulator or testflight placeholder and icon is dissappear. How can I solve it?
Here my code:
view:
private lazy var searchBar: UISearchBar = {
let view = UISearchBar()
view.delegate = self
view.barTintColor = UIColor.clear
view.backgroundColor = UIColor.clear
view.isTranslucent = true
view.setBackgroundImage(UIImage(), for: .any, barMetrics: .default)
view.placeholder = "Search"
return view
}()
setup method:
view.addSubview(searchBar)
view.addSubview(collectionView)
searchBar.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide).inset(17)
make.leading.trailing.equalToSuperview().inset(6)
make.height.equalTo(40)
}
I'm building a SwiftUI app with a UITextView subclass, and it seems that the software keyboard doesn't trigger the pressesBegan or pressesEnded functions of UITextView. With a hardware keyboard, pressesBegan works as expected, allowing us to intercept key presses in our subclass.
I can't find any documentation about this, or any other forum posts (here or on Stack Overflow) that talk about a discrepancy between software and hardware keyboard behaviors, and I can't believe this is an intended behavior. Our app is a SwiftUI app, in case that's relevant.
Does anyone have any guidance? Is this a bug or am I not understanding this API? Any information or work arounds would be greatly appreciated.
I've made a sample project that demonstrates this issue, which you can grab from GitHub at https://github.com/nyousefi/KeyPressSample. To see this in action, run the sample project and start pressing keys. The hardware keyboard will print the key press at the top of the screen (above the text view), while the software keyboard won't.
Hi, I'm running into a crash I can't wrap my head around. I'm using a collectionView with a compositional layout. Upon reloading the collection view via reloadData or reloading a particular section via reloadSection, customers are running into a crash I'm unable to reproduce.
The only information is this:
Fatal Exception: NSInternalInconsistencyException
UICollectionView internal inconsistency: missing final attributes for cell <UICollectionViewListCell: 0x13ad42a40; frame = (0 791; 768 40); layer = <CALayer: 0x28268e0e0>>; initial attributes: <UICollectionViewLayoutAttributes: 0x13ac5aa90> index path: (<NSIndexPath: 0xb33e6ceee91dbb86> {length = 2, path = 4 - 0}); frame = (0 791; 768 40); ; layout query: <UICollectionViewLayoutAttributes: 0x137db5190> index path: (<NSIndexPath: 0xb33e6ceee91dbb86> {length = 2, path = 4 - 0}); frame = (0 800; 768 44); ; collection view: <UICollectionView: 0x13d120a00; frame = (0 0; 768 904); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x2829ad7d0>; layer = <CALayer: 0x2827f1cc0>; contentOffset: {0, 0}; contentSize: {768, 1715}; adjustedContentInset: {0, 0, 0, 0}; layout: <UICollectionViewCompositionalLayout: 0x13ad2bd50>; dataSource: <drchrono_EHR.AppointmentDetailViewController: 0x139fff600>>
Any help would be greatly appreciated.
Thank you.
I have a UICalendarView that is embedded in a UIStackView. When I hide/show items in the stack and cause the height of the stack to change, the UICalendarView animates unexpectedly:
Note that the height and width of the UICalendarView remain unchanged and the animation appears to leave the actual content unchanged.
Interestingly, if I select a month with 6 weeks it does not do this animation:
Nothing I have tried has allowed me to avoid this distracting and unnecessary animation. Any thoughts as to why this is happening or, even better, is there anything I can do to avoid it?
Appreciate the help!
feedbackassistant: https://feedbackassistant.apple.com/feedback/14724519 (FB14724519)
We are developing an app using UIPageViewController. We have configured the UIPageViewController to provide 1 or 2 pages with a .pageCurl animation.
We discovered a scenario where the viewControllerBefore and viewControllerAfter methods of the UIPageViewControllerDataSource are being called excessively. This happens when dragging on the last page, including dragging along the vertical axis (e.g., dragging from the top right to the bottom left). In this scenario, crashes occur intermittently, and we have observed similar issues in Apple Books as well.
We would like to eliminate or minimize these crashes. Unfortunately, due to design constraints, we cannot remove the animation or adjust the page transition speed.
Questions:
Are there any updates or news regarding this issue, such as changes in the UIKit framework?
What is the best way to prevent or minimize this crash?
Crash Informations:
The number of view controllers provided (0) doesn't match the number required (2) for the requested transition
The number of view controllers provided (0) doesn't match the number required (1) for the requested transition
Crash Videos:
https://www.dropbox.com/scl/fo/bz7ykvm41du29u03ywbwo/AHO1y7CxURIi7s2QrERxPZk?rlkey=ugavf4tqo22q60g5bexe3kguz&e=1&st=xao8ypm6&dl=0
Topic:
UI Frameworks
SubTopic:
UIKit
The code for the issue is attached below.
Hello,
I am trying to implement a custom UICollectionViewLayout that does the following:
Everything works great for the most part, however I have encountered some unexpected animations when applying a new snapshot:
As you can see, any cell that contains a custom view with a height set with AutoLayout is scaled vertically before animating to it's intended height.
Here is a simple Xcode project that demonstrates the issue. Tap on the plus sign in the top right corner and watch the cells.
Example project: https://we.tl/t-9Y25NHzxiI
Custom UICollectionViewLayout code:
final class CustomLayout: UICollectionViewLayout {
struct PMCardContainerLayoutCell: Equatable {
var column: Int
var row: Int
}
// Configurable properties
public var numberOfColumns: Int = 6
public var cellHeight: Double = 100
public var cellSpacing: Double = 20
public var rowSpacing: Double = 20
public var sectionInsets: NSDirectionalEdgeInsets = .zero
public var layoutAttributes: [IndexPath: UICollectionViewLayoutAttributes] = [:]
override func prepare() {
super.prepare()
guard let collectionView else {
return
}
var updatedLayoutAttributes: [IndexPath: UICollectionViewLayoutAttributes] = [:]
let columnWidth: Double = (collectionView.bounds.width - cellSpacing * Double(numberOfColumns - 1) - sectionInsets.leading - sectionInsets.trailing) / Double(numberOfColumns)
let numberOfSections: Int = collectionView.numberOfSections
for section in 0..<numberOfSections {
var occupiedCells: [PMCardContainerLayoutCell] = []
var currentColumn: Int = 0
var currentRow: Int = 0
let numberOfItems: Int = collectionView.numberOfItems(inSection: section)
for item in 0..<numberOfItems {
let itemIndexPath = IndexPath(item: item, section: section)
let itemAttributes = UICollectionViewLayoutAttributes(forCellWith: itemIndexPath)
let itemSpanColumn = 1
let itemHeight = layoutAttributes[itemIndexPath]?.bounds.height ?? 140
let itemSpanRow = Int(ceil(itemHeight / (cellHeight + rowSpacing)))
let itemWidth = columnWidth * Double(itemSpanColumn) + cellSpacing * (Double(itemSpanColumn) - 1)
while true {
var itemDoesFit: Bool = true
if currentColumn + itemSpanColumn > numberOfColumns {
currentColumn = 0
currentRow += 1
}
for cell in 0..<itemSpanColumn {
if occupiedCells.contains(.init(column: currentColumn + cell, row: currentRow)) {
itemDoesFit = false
}
}
if itemDoesFit {
break
}
currentColumn += itemSpanColumn
}
if itemSpanRow > 1 {
for row in 1..<itemSpanRow {
for column in 0..<itemSpanColumn {
occupiedCells.append(.init(column: currentColumn + column, row: currentRow + row))
}
}
}
let originX = sectionInsets.leading + columnWidth * Double(currentColumn) + cellSpacing * Double(currentColumn)
let originY = + cellHeight * Double(currentRow) + rowSpacing * Double(currentRow)
itemAttributes.frame = CGRect(
x: originX,
y: originY,
width: itemWidth,
height: itemHeight
)
itemAttributes.zIndex = itemIndexPath.section * 10 + itemIndexPath.item
updatedLayoutAttributes[itemIndexPath] = itemAttributes
currentColumn += itemSpanColumn
}
}
layoutAttributes = updatedLayoutAttributes
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var allAttributes: [UICollectionViewLayoutAttributes] = []
for (_, attributes) in layoutAttributes {
if (rect.intersects(attributes.frame)) {
allAttributes.append(attributes)
}
}
return allAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return layoutAttributes[indexPath]
}
override var collectionViewContentSize: CGSize {
guard let collectionView else {
return .zero
}
let contentHeight: CGFloat = layoutAttributes.map({ $0.value.frame.maxY }).max() ?? 0
return CGSize(width: collectionView.bounds.width, height: contentHeight)
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
override func shouldInvalidateLayout(forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes, withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes) -> Bool {
return originalAttributes.frame.height != preferredAttributes.frame.height
}
override func invalidationContext(forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes, withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutInvalidationContext {
layoutAttributes[preferredAttributes.indexPath]?.frame.size = preferredAttributes.frame.size
let context = super.invalidationContext(forPreferredLayoutAttributes: preferredAttributes, withOriginalAttributes: originalAttributes)
return context
}
public override func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) {
super.invalidateLayout(with: context)
if context.invalidateEverything || context.invalidateDataSourceCounts {
layoutAttributes.removeAll()
}
}
}
Anyone have any idea what I am doing wrong?
Thank you!