Construct and manage graphical, event-driven user interfaces for iOS or tvOS apps using UIKit.

UIKit Documentation

Post

Replies

Boosts

Views

Activity

State Restoration: Deleting current state data
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?
0
0
8
37m
iOS 18 beta - Cancelling Interactive UICollectionView Layout Transition causes the collectionView to disappear
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!
0
0
43
6h
Implementing Undo/Redo Feature in UITextView with IME Support
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?
0
0
47
14h
Can an automaker app and a Carplay app (when automaker is not possible) live in the same App?
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.
1
0
29
15h
UIGraphicsImageRenderer memory issue (iOS 17.5.1)
Testing on iPhone 12 mini, I have encountered a weird situation. I am try to take snapshot of my view, which works fine but the memory is never released after the snapshot is taken. func screenshot(view: UIView, scale:Double) -> URL? { guard let containerView = view.superview, let containerSuperview = containerView.superview else { return nil } let rendererFormat = UIGraphicsImageRendererFormat() rendererFormat.scale = scale var renderer = UIGraphicsImageRenderer(bounds: containerView.frame, format: rendererFormat) let image = autoreleasepool { return renderer.image { context in containerSuperview.drawHierarchy(in: containerSuperview.layer.frame, afterScreenUpdates: true) //memory hog starts from here } } guard let data = image.heicData() else { return nil } //more code to save data to file URL and return it } initially it appears to work normally but as soon as I change the scale: rendererFormat.scale = 10 I can see a spike in memory but the problem is then the memory is never released even after the image is saved. so initially, the app uses: 35MB memory -> when processing the memory usage jumps to expected 250MB to 300MB to process large image -> after processing the memory goes down to around 90MB to 120MB but it never really returns to it's original 35MB state. Is this a bug or this is expected? If this is expected behaviour then is there any low level API to free the memory after it's job is done.
1
0
82
2d
View with 2 labels of dynamic height
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?
2
0
64
3d
Change in title bar behavior in Catalyst app when building under Xcode 16
I'm running into a change in behavior with the title bar when building our Catalyst app under Xcode 16 versus what we are seeing with the same code built under Xcode 15. The title bar is hidden, as it should be in the Xcode 15 build. However, when building the same project under Xcode 16, the title bar is shown with the center contents of the navigation bar in the right pane being duplicated in the title bar (see screenshot at the end), which is undesirable. The screenshots for both builds were taken in macOS Sequoia Beta Seed 1. The Catalyst Interface setting is configured to "Optimize for Mac" in the project file. The scene's titlebar.titleVisibility property is set to .hidden, titlebar.toolbar is set to nil. UINavigationBar's appearance is configured with a preferredBehaviorStyle of .pad when running under Catalyst 16 or later. Is this a bug or am I missing a flag or something that was introduced to Catalyst/UIKit that I've overlooked? I've also filed a feedback for this issue: FB14000006. Thank you for your time.
0
0
66
4d
UICollectionView scrolls back to 0 using comp layout
PLATFORM AND VERSION: iOS Development environment: Xcode 15.2 macOS 13.6.3 iOS 17.2 DESCRIPTION OF PROBLEM Using a UICollectionView with compositional layout, once the view is created, programmatically scroll to a row using the scrollToItem method. For this report I created a simple dataSource with 400 rows and 1 section. Scrolling to row 200 in viewDidLoad or viewWillAppear after calling collectionView.layoutIfNeeded(). The compositional layout's orthogonalScrollingBehavior is set to .groupPagingCentered The collectionView behaves as expected and shows row 200. Issue The issue is that when swiping too quickly to go to row 199 or prior the entire collectionView is reset to row 0. This does not happen when swiping to go on row 201 or above. STEPS TO REPRODUCE Run the app on iPhone. Swipe right quickly (to go to row prior to 200) Expected result: row 199 or 198 is shown Actual result: collectionView is reset to row 0
2
0
86
4d
IOS 17.4 - 17.5 bug on copy html type of text
Hello we have created a function that is expanding copy module to support html format. Everything inside that function works fine but on 17.4+ IOS version copying the html element strike-through tag is not working (other HTML elements are working fine) . Looking the logs seems like are getting stripped. Here is the code: void copyToClipboard(NSString *htmlContent) { UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; [pasteboard setValue:htmlContent forPasteboardType:@"public.html"]; } Does anyone know fix for this or when this will be fixed or will it be fixed in next update?
0
0
70
5d
uncaught exception 'NSInternalInconsistencyException', reason: 'Layout requested for visible navigation bar.
Hey,bros: I meet this crash,but I can't find any useful infos to fix this crash.And this crash only happens on iOS17. Here are the crash infos: *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Layout requested for visible navigation bar, <UINavigationBar: 0x106128820; frame = (0 0; 430 44); opaque = NO; autoresize = W; tintColor = UIExtendedGrayColorSpace. Here are the stacks: Thread 0 name: com.apple.main-thread CoreFoundation ___exceptionPreprocess (in CoreFoundation) libobjc.A.dylib _objc_exception_throw (in libobjc.A.dylib) Foundation -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] (in Foundation) UIKitCore -[UINavigationBar layoutSubviews] (in UIKitCore) UIKitCore -[UIView(CALayerDelegate) layoutSublayersOfLayer:] (in UIKitCore) longbridge-ios-app -[UIView(Thread) ex_layoutSublayersOfLayer:] (in longbridge-ios-app:UIView+Thread.m:36) UIKitCore -[UINavigationBar layoutSublayersOfLayer:] (in UIKitCore) QuartzCore CA::Layer::layout_if_needed(CA::Transaction*) (in QuartzCore) UIKitCore -[UIView(Hierarchy) layoutBelowIfNeeded] (in UIKitCore) UIKitCore -[UINavigationController _positionNavigationBarHidden:edge:initialOffset:] (in UIKitCore) UIKitCore -[UINavigationController _positionNavigationBarHidden:edge:] (in UIKitCore) UIKitCore -[UINavigationController _updateBarsForCurrentInterfaceOrientationAndForceBarLayout:] (in UIKitCore) UIKitCore -[UIViewController viewDidMoveToWindow:shouldAppearOrDisappear:] (in UIKitCore) UIKitCore -[UINavigationController viewDidMoveToWindow:shouldAppearOrDisappear:] (in UIKitCore) UIKitCore -[UIView(Internal) _didMoveFromWindow:toWindow:] (in UIKitCore) UIKitCore ___45-[UIView(Hierarchy) _postMovedFromSuperview:]_block_invoke (in UIKitCore) CoreAutoLayout -[NSISEngine withBehaviors:performModifications:] (in CoreAutoLayout) UIKitCore -[UIView _postMovedFromSuperview:] (in UIKitCore) UIKitCore -[UIView(Internal) _addSubview:positioned:relativeTo:] (in UIKitCore) UIKitCore -[UITransitionView transition:fromView:toView:removeFromView:] (in UIKitCore) UIKitCore -[UIViewControllerBuiltinTransitionViewAnimator animateTransition:] (in UIKitCore) UIKitCore ____UIViewControllerTransitioningRunCustomTransition_block_invoke_3 (in UIKitCore) UIKitCore +[UIKeyboardSceneDelegate _pinInputViewsForKeyboardSceneDelegate:onBehalfOfResponder:duringBlock:] (in UIKitCore) UIKitCore ____UIViewControllerTransitioningRunCustomTransition_block_invoke_2 (in UIKitCore) UIKitCore +[UIView(Animation) _setAlongsideAnimations:toRunByEndOfBlock:] (in UIKitCore) UIKitCore __UIViewControllerTransitioningRunCustomTransition (in UIKitCore) UIKitCore ___56-[UIPresentationController runTransitionForCurrentState]_block_invoke_3 (in UIKitCore) UIKitCore -[_UIAfterCACommitBlock run] (in UIKitCore) UIKitCore -[_UIAfterCACommitQueue flush] (in UIKitCore) UIKitCore __runAfterCACommitDeferredBlocks (in UIKitCore) UIKitCore __cleanUpAfterCAFlushAndRunDeferredBlocks (in UIKitCore) UIKitCore __UIApplicationFlushCATransaction (in UIKitCore) UIKitCore __UIUpdateSequenceRun (in UIKitCore) UIKitCore _schedulerStepScheduledMainSection (in UIKitCore) UIKitCore _runloopSourceCallback (in UIKitCore) CoreFoundation _CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION (in CoreFoundation) CoreFoundation ___CFRunLoopDoSource0 (in CoreFoundation) CoreFoundation ___CFRunLoopDoSources0 (in CoreFoundation) CoreFoundation ___CFRunLoopRun (in CoreFoundation) CoreFoundation _CFRunLoopRunSpecific (in CoreFoundation) GraphicsServices _GSEventRunModal (in GraphicsServices) UIKitCore -[UIApplication _run] (in UIKitCore) UIKitCore _UIApplicationMain (in UIKitCore) longbridge-ios-app main (in longbridge-ios-app:main.m:22) dyld start (in dyld)
1
0
57
5d
Diffable datasource and how to add new items / use snapshots properly
First question: I'd like to get clarification on what code I have to write to add a new item to a UICollectionView. So far, I have managed to seed my datasource with some sample data, which correctly appears in the collectionView. snapshot.appendSections(sections) // assume there is only 1 section. snapshot.appendItems(["addBoat"], toSection: .Boat)// placeholder for an add button snapshot.appendItems(backingStore.BoatIdentifiers(), toSection: .Boats)// add the boats in the backing store. dataSource.apply(snapshot, animatingDifferences: false) After the user enters the necessary data for a new boat, my code creates a new item instance (a struct). The sample project DiffableDataSourceSample README states: While a diffable data source can determine the changes between its current snapshot and a new one, it doesn't monitor the data collection for changes. Instead, it's the responsibility of the app to detect data changes and tell the diffable data source about those changes, by applying a new snapshot. So does that really mean that when I add one new item, I have to 1) add it to my backing store, and then 2) create a new snapshot with all the data in my backing store so that I can 3) apply that to the dataSource? Intuitively, if my backing store has a couple thousand items, step 2 feels it could be very costly. I was expecting that I could just append the new item to the existing snapshot (the one I created when seeding the data). But that means I have to keep the snapshot alive (and pass it around viewControllers). Something like this: snapshot.appendItems(newBoat.id, toSection: .Boats) Second, related question: I need to check that a new item doesn't already exist. Do I need to check in the existing snapshot? Like this: if let index = snapshot.indexOfItem(newBoat.id) { ... // boat is duplicate, don't allow adding it. } Or do I check only in my backing store, and then do steps 1 2 and 3 above? Thanks for any help.
1
0
51
6d
iOS18 WebView Crash
There is a 'WebCore::CachedResource::removeClient(WebCore::CachedResourceClient&)' crash in iOS 18 version. Could you please tell me if there are any changes to the WebView regarding this crash and how to debug it?
0
0
99
6d
UISplitViewController column-style, bug in setViewController?
&TLDR; I see no documentation stating that during the life of UISplitViewController, that I cannot call setViewController(xxxxx, column) more than once on the same column. Yet in my experience calling it a second time does not work, unless I set the value to nil before calling it again to set it to the new value... I'm using a UISplitViewController in newer column-style layout and when bootstrapping the app, I create the UISplitViewController and use setViewController(primaryViewController, .primary) and setViewController(detailViewController, .secondary). In order to use the primaryViewController for the compact scenario, I return the .primary column in the UISplitViewControllerDelegate method: splitViewController(_ svc: UISplitViewController, topColumnForCollapsingToProposedTopColumn proposedTopColumn: UISplitViewController.Column) -> UISplitViewController.Column Tapping a cell in the primaryViewController, results in a call to splitViewController.show(.secondary) This works for both split view (primary + secondary) as well as in compact mode. Tapping a cell in the secondary viewController results in pushing a new sub-detail onto the navigation sack. Again so far so good. If I switch back to split mode (primary+secondary) it still looks good with secondary showing the sub-detail. If I then collapse into compact view, the primary is once again shown. If I tap a different cell in the primary, the same call to: splitViewController.show(.secondary) results in the sub-detail viewController being shown. Instead, I want the secondary to show the detail for the new selected cell...not the sub-detail of the prior cell. To fix this, I popped the navigation stack for the secondary to rootViewController if secondaryNavigationController.topViewController as? DetailViewController == nil { secondaryNavigationController.popToRootViewController(animated: false) next, I attempted to replace splitViewController's secondary viewController by assigning the secondaryNavigationController which now has the DetailViewController as the top (and only) viewController. splitViewController.setViewController(secondaryNavigationController, for: .secondary) after this assignment, calling splitViewController.viewController(for: .secondary) returns the old sub-detail viewController, not the expected DetailViewController! IMO this is a bug. I found the following solution. First set it to nil, then set it to the desired value. splitViewController.setViewController(nil, for: .secondary) splitViewController.setViewController(secondaryNavigationController, for: .secondary)
0
0
72
6d
Why is KeyboardLayoutGuide not applied in viewIsAppearing() in iOS 15.0?
viewIsAppearing is available in iOS 13 and above, and I understand that setting the layout in this lifecycle is a good way to do it. But when I use it, confirmed that UIKeyboardLayout does not work properly in viewIsAppearing in iOS versions lower than 16.0. There is a small problem not only with viewIsAppearing, but also with viewDidLoad(). In viewDidLoad(), the component to which UIKeyboardLayoutGuide is applied is visible when the screen first appears. However, there is a problem where the layout with UIKeyboardLayoutGuide applied is not visible when returning after changing the navigation screen (Push & Pop). I confirmed that UIKeyboardLayoutGuide is applied properly in viewIsAppearing and viewDidLoad in iOS 16.0 and higher versions. Below is the code and environment for reproducing the problem situation. ==================================================== Envirionment: M1 Pro(Sonoma v14.5), Xcode(v15.4), iPhone Simulator (iOS v15.0, v15.2) import UIKit import SnapKit final class ViewController: UIViewController { private let uiTextField: UITextField = { $0.placeholder = "search" $0.backgroundColor = .red return $0 }(UITextField()) override func viewIsAppearing(_ animated: Bool) { super.viewIsAppearing(animated) view.backgroundColor = .white layout() } private func layout() { view.addSubview(uiTextField) uiTextField.snp.makeConstraints { $0.height.equalTo(50) $0.horizontalEdges.equalToSuperview() $0.bottom.equalTo(view.keyboardLayoutGuide.snp.top) } } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { view.endEditing(true) } } I wonder if I haven't checked what I need to check yet, or if there are other problems.
0
0
92
1w