Construct 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

Correct Collection View "stretchy header" implementation?
Hello, I have the following subclass of UICompositionalCollectionViewLayout to get the stretchy header effect as shown below. It works quite well, but I don't really have an experience with creating custom layouts so I thought I'd ask if maybe my implementation doesn't have some important flaws. I once ran into persistent layout loop crash with this and I am not sure what exactly I changed but it stopped happening. However since I am using this layout on important screen, I would like to make sure there isn't obvious potential for the layout loop crash happening in App Store version. I am particularly unsure about the shouldInvalidateLayout implementation. Originally I was returning true all the time, but decided to change it and only force invalidation for negative content offset which is when my header is supposed to stretch. Here is the full code: final class StretchyCompositionalLayout: UICollectionViewCompositionalLayout { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { var attrs = super.layoutAttributesForElements(in: rect) ?? [] guard let collectionView = collectionView else { return attrs } let contentOffset = collectionView.contentOffset.y guard contentOffset < 0 else { return attrs } var newAttributes: UICollectionViewLayoutAttributes? attrs.forEach({ attribute in if attribute.indexPath.section == 0 && attribute.indexPath.item == 0 { let startFrame = attribute.frame newAttributes = attribute.copy() as? UICollectionViewLayoutAttributes let newFrame: CGRect = .init(x: 0, y: contentOffset, width: startFrame.width, height: startFrame.height - contentOffset) newAttributes?.frame = newFrame } }) if let new = newAttributes { attrs.removeAll { attr in return attr.indexPath.section == 0 && attr.indexPath.item == 0 } attrs.insert(new, at: 0) } return attrs } override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { guard let attributes = super.layoutAttributesForItem(at: indexPath) else { return nil } let contentOffset = collectionView?.contentOffset.y ?? 1 guard contentOffset < 0 else { return attributes } if indexPath.section == 0 && indexPath.item == 0 { let attributes = attributes.copy() as? UICollectionViewLayoutAttributes ?? attributes let startFrame = attributes.frame let newFrame: CGRect = .init(x: 0, y: contentOffset, width: startFrame.width, height: startFrame.height - contentOffset) attributes.frame = newFrame return attributes } else { return super.layoutAttributesForItem(at: indexPath) } } override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { let contentOffset = collectionView?.contentOffset.y ?? 1 // There is visual glitch when 0 is used in this condition if contentOffset < 1 { return true } else { return super.shouldInvalidateLayout(forBoundsChange: newBounds) } } } Any feedback welcome!
0
0
66
1d
Inconsistency on view lifecycle events between UIKit and SwiftUI when using UIVPageViewController
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.
0
0
65
2d
UI Tab Bar
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() } }
1
0
106
1d
iOS 18. UINavigationController stack changes with delay
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 ?
0
0
61
4d
iOS 18 Translation API availability for UIKit
After reading the documentation for TranslationSession and other API methods I got an impression that it will not be possible to use the Translation API with UI elements built with UIKit. You don’t instantiate this class directly. Instead, you obtain an instance of it by adding a translationTask(_:action:) or translationTask(source:target:action:) function to the SwiftUI view containing the content you want to translate So the main question I have to the engineers of mentioned framework is will there be any support for UIKit or app developers will have to rewrite their apps in SwiftUI just to be able to use the Translation API?
0
1
196
6d
Crash After Presenting SwiftUI Alert
I’m seeing a crash in production for a small percentage of users, and have narrowed it down based on logging to happening as or very shortly after an alert is presented using SwiftUI. This seems to be isolated to iOS 17.5.1, but since it’s a low-volume crash I can’t be sure there aren’t other affected versions. What can I understand from the crash report? Here’s a simplified version of the code which presents the alert, which seems so simple I can’t understand why it would crash. And following that is the crash trace. // View (simplified) @MainActor public struct MyView: View { @ObservedObject var model: MyViewModel public init(model: MyViewModel) { self.model = model } public var body: some View { myViewContent .overlay(clearAlert) } var clearAlert: some View { EmptyView().alert( "Are You Sure?", isPresented: $model.isClearAlertVisible, actions: { Button("Keep", role: .cancel) { model.clearAlertKeepButtonWasPressed() } Button("Delete", role: .destructive) { model.clearAlertDeleteButtonWasPressed() } }, message: { Text("This cannot be undone.") } ) } } // Model (simplified) @MainActor public final class MyViewModel: ObservableObject { @Published var isClearAlertVisible = false func clearButtonWasPressed() { isClearAlertVisible = true } func clearAlertKeepButtonWasPressed() { // No-op. } func clearAlertDeleteButtonWasPressed() { // Calls other code. } } Incident Identifier: 36D05FF3-C64E-4327-8589-D8951C8BAFC4 Distributor ID: com.apple.AppStore Hardware Model: iPhone13,2 Process: My App [379] Path: /private/var/containers/Bundle/Application/B589E780-96B2-4A5F-8FCD-8B34F2024595/My App.app/My App Identifier: com.me.MyApp Version: 1.0 (1) AppStoreTools: 15F31e AppVariant: 1:iPhone13,2:15 Code Type: ARM-64 (Native) Role: Foreground Parent Process: launchd [1] Coalition: com.me.MyApp [583] Date/Time: 2024-06-21 20:09:20.9767 -0500 Launch Time: 2024-06-20 18:41:01.7542 -0500 OS Version: iPhone OS 17.5.1 (21F90) Release Type: User Baseband Version: 4.50.06 Report Version: 104 Exception Type: EXC_BREAKPOINT (SIGTRAP) Exception Codes: 0x0000000000000001, 0x00000001a69998c0 Termination Reason: SIGNAL 5 Trace/BPT trap: 5 Terminating Process: exc handler [379] Triggered by Thread: 0 Kernel Triage: VM - (arg = 0x3) mach_vm_allocate_kernel failed within call to vm_map_enter VM - (arg = 0x3) mach_vm_allocate_kernel failed within call to vm_map_enter VM - (arg = 0x3) mach_vm_allocate_kernel failed within call to vm_map_enter VM - (arg = 0x3) mach_vm_allocate_kernel failed within call to vm_map_enter VM - (arg = 0x3) mach_vm_allocate_kernel failed within call to vm_map_enter Thread 0 name: Thread 0 Crashed: 0 libswiftCore.dylib 0x00000001a69998c0 _assertionFailure(_:_:file:line:flags:) + 264 (AssertCommon.swift:144) 1 AttributeGraph 0x00000001d0cd61a4 Attribute.init<A>(body:value:flags:update:) + 352 (Attribute.swift:473) 2 SwiftUI 0x00000001ac034054 closure #1 in Attribute.init<A>(_:) + 128 (<compiler-generated>:0) 3 SwiftUI 0x00000001ac033cac partial apply for closure #1 in Attribute.init<A>(_:) + 32 (<compiler-generated>:0) 4 libswiftCore.dylib 0x00000001a6ad0450 withUnsafePointer<A, B>(to:_:) + 28 (LifetimeManager.swift:128) 5 SwiftUI 0x00000001ad624d14 closure #2 in UIKitDialogBridge.startTrackingUpdates(actions:) + 268 (UIKitDialogBridge.swift:370) 6 SwiftUI 0x00000001ad624ae0 UIKitDialogBridge.startTrackingUpdates(actions:) + 248 (UIKitDialogBridge.swift:369) 7 SwiftUI 0x00000001ad6250cc closure #4 in UIKitDialogBridge.showNewAlert(_:id:) + 72 (UIKitDialogBridge.swift:471) 8 SwiftUI 0x00000001abfdd050 thunk for @escaping @callee_guaranteed () -> () + 36 (:-1) 9 UIKitCore 0x00000001aa5722e4 -[UIPresentationController transitionDidFinish:] + 1096 (UIPresentationController.m:651) 10 UIKitCore 0x00000001aa571d88 __56-[UIPresentationController runTransitionForCurrentState]_block_invoke.114 + 320 (UIPresentationController.m:1390) 11 UIKitCore 0x00000001aa5cb9ac -[_UIViewControllerTransitionContext completeTransition:] + 116 (UIViewControllerTransitioning.m:304) 12 UIKitCore 0x00000001aa34a91c __UIVIEW_IS_EXECUTING_ANIMATION_COMPLETION_BLOCK__ + 36 (UIView.m:16396) 13 UIKitCore 0x00000001aa34a800 -[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:] + 624 (UIView.m:16429) 14 UIKitCore 0x00000001aa349518 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 436 (UIView.m:0) 15 UIKitCore 0x00000001aa356b14 -[UIViewAnimationState animationDidStop:finished:] + 192 (UIView.m:2400) 16 UIKitCore 0x00000001aa356b84 -[UIViewAnimationState animationDidStop:finished:] + 304 (UIView.m:2422) 17 QuartzCore 0x00000001a96f8c50 run_animation_callbacks(void*) + 132 (CALayer.mm:7714) 18 libdispatch.dylib 0x00000001aff61dd4 _dispatch_client_callout + 20 (object.m:576) 19 libdispatch.dylib 0x00000001aff705a4 _dispatch_main_queue_drain + 988 (queue.c:7898) 20 libdispatch.dylib 0x00000001aff701b8 _dispatch_main_queue_callback_4CF + 44 (queue.c:8058) 21 CoreFoundation 0x00000001a808f710 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 16 (CFRunLoop.c:1780) 22 CoreFoundation 0x00000001a808c914 __CFRunLoopRun + 1996 (CFRunLoop.c:3149) 23 CoreFoundation 0x00000001a808bcd8 CFRunLoopRunSpecific + 608 (CFRunLoop.c:3420) 24 GraphicsServices 0x00000001ecf3c1a8 GSEventRunModal + 164 (GSEvent.c:2196) 25 UIKitCore 0x00000001aa6c490c -[UIApplication _run] + 888 (UIApplication.m:3713) 26 UIKitCore 0x00000001aa7789d0 UIApplicationMain + 340 (UIApplication.m:5303) 27 SwiftUI 0x00000001ac27c148 closure #1 in KitRendererCommon(_:) + 168 (UIKitApp.swift:51) 28 SwiftUI 0x00000001ac228714 runApp<A>(_:) + 152 (UIKitApp.swift:14) 29 SwiftUI 0x00000001ac2344d0 static App.main() + 132 (App.swift:114) 30 My App 0x00000001001e7bfc static MyApp.$main() + 52 (MyApp.swift:0) 31 My App 0x00000001001e7bfc main + 64 32 dyld 0x00000001cb73de4c start + 2240 (dyldMain.cpp:1298)
2
0
120
15h
UIVisualEffectView isn't blurred during the device rotation
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) { } }
0
0
77
6d
UITabBarController render glitch since iOS18 beta 1 & 2 when activating tabs.
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.
1
0
135
6d
React-Native iOS app is crashing immediately upon launch when creating development build
I have created an app for iOS using React Native. I've gotten the app to a point where it works on the iPhone simulator via the Expo App. It even works when creating a build of the app to distribute using TestFlight. Now, I want to add Google Ads into the app. However, I've learned that Google Ads won't work via an Expo build. Instead one has to create a development build to test the ads in the app and see how they look. I've also finally been able to compile a successful development build using eas (expo application services). When I install the app on the simulator and try to open it however, the app crashes almost immediately and I don't know why. I have tried deciphering the symbolicated crash report, but I have not been able to figure out what could be causing the error. It looks like it has something to do with the user interface. Below is the thread where the app crashes: Thread 0 Crashed:: Dispatch queue: com.apple.main-thread 0 libsystem_kernel.dylib 0x11324a14a __pthread_kill + 10 1 libsystem_pthread.dylib 0x1132abebd pthread_kill + 262 2 libsystem_c.dylib 0x7ff80016dd1c abort + 133 3 libc++abi.dylib 0x7ff8002c6d12 abort_message + 241 4 libc++abi.dylib 0x7ff8002b951a demangling_terminate_handler() + 266 5 libobjc.A.dylib 0x7ff800061fba _objc_terminate() + 96 6 libc++abi.dylib 0x7ff8002c616b std::__terminate(void (*)()) + 6 7 libc++abi.dylib 0x7ff8002c6126 std::terminate() + 54 8 libdispatch.dylib 0x7ff8001796ec _dispatch_client_callout + 28 9 libdispatch.dylib 0x7ff80017d1e2 _dispatch_block_invoke_direct + 508 10 FrontBoardServices 0x7ff807a8b3a7 __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 30 11 FrontBoardServices 0x7ff807a8b281 -[FBSMainRunLoopSerialQueue _targetQueue_performNextIfPossible] + 188 12 FrontBoardServices 0x7ff807a8b3cf -[FBSMainRunLoopSerialQueue _performNextFromRunLoopSource] + 19 13 CoreFoundation 0x7ff800429ff3 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17 14 CoreFoundation 0x7ff800429f35 __CFRunLoopDoSource0 + 157 15 CoreFoundation 0x7ff800429732 __CFRunLoopDoSources0 + 215 16 CoreFoundation 0x7ff800423e67 __CFRunLoopRun + 919 17 CoreFoundation 0x7ff8004236ed CFRunLoopRunSpecific + 557 18 GraphicsServices 0x7ff8103ba08f GSEventRunModal + 137 19 UIKitCore 0x7ff805cdf6ee -[UIApplication _run] + 972 20 UIKitCore 0x7ff805ce416e UIApplicationMain + 123 21 LeftOff 0x10f870380 main + 96 22 dyld_sim 0x112d3a3e0 start_sim + 10 23 dyld 0x113d57366 start + 1942 I have been trying to solve this for some days now and could really use some help. I have tried changing versions of dependencies in my app too, with a focus on those that manage the UI. For example, I have changed packages such as react-native-ranimated, react-navigation/stack, react-native-gesture-handler, and react-native-screens to name a few. Nothing has worked so far. Please help.
2
0
173
6d
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
90
1w
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
111
1w
White text on white background
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?
1
0
87
1w
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
88
1w
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
97
1w
SwiftUI SideMenu Navigation Issue
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.
1
0
160
1w
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
112
1w