Using Apple Pencil with SwiftUI text editor, when the user presses and holds in the middle of text already in the editor box a crash occurs. This is very reproducible even on this minimal example.
import SwiftUI
struct ContentView: View {
@State private var text: String = "The quick brown fox jumps over the lazy dog"
var body: some View {
VStack {
TextEditor(text: $text)
.background(Color.init(red: 242, green: 242, blue: 242))
.border(.black)
.frame(height: 200)
}
.padding()
}
}
When tapping and holding somewhere in the middle of the sentence, for example on the word "jumps" the app crashes with an exception reading:
Thread 1: "NSTextContentStorage: Inconsistent element cache state. Elements for range {0, 43} are already cached while trying to insert"
The stack trace is:
I have attempted to use the approach discussed in this post to make the underlying UITextView use TextKit 1, but could find no UITextView in the underlying UIView hierarchy. Any advice would be greatly appreciated.
Post
Replies
Boosts
Views
Activity
Our app uses a number of collection views in a single iPad screen for the user to set the location of various resources. There is a main group for unassigned resources and up to 16 UICollectionView's that represent a job for the resource. The user can manage their resources by dragging a resource between any of the collection views. This generally works but periodically after dragging back and forth many times we get the following runtime exception:
Shadow insert is nil. File a bug on UICollectionView!
When I search for this error I get no results. The drag and drop implementation is as follows:
public func collectionView(_ collectionView: UICollectionView, canHandle session: UIDropSession) -> Bool {
if !singleUseCollectionViews.contains(collectionView){
return true
}
guard let containerProperties = getResourceContainer(collectionView: collectionView) else {
return false
}
return viewModel.canResourceBeDropped(containerToDropTo: containerProperties)
}
public func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
currentDestinationIndexPath = destinationIndexPath
// Copy is being used to add the green plus icon when user is dropping item into the other collectionview.
return UICollectionViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath)
}
public func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
defer {
viewModel.isUserDraggingResource = false
}
guard let containerToDropTo = getResourceContainer(collectionView: collectionView, sectionIndex: 0) else {
print("Attempting to drop where resource cannot be dropped")
assertionFailure()
return
}
if let resourceToDrop = self.viewModel.resourceToDrop {
self.viewModel.addNewResourceToGroup(resource: resourceToDrop,
containerProperties: containerToDropTo,
userInitiated: true)
} else if self.viewModel.personnelToDrop != nil {
self.viewModel.formNewResourcesWithDroppedPersonnel(containerToDropTo: containerToDropTo)
}
resourceDropped()
}
public func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
guard viewModel.boardIsActive else {
return []
}
guard let stringData = "test".data(using: .utf8) else {
return []
}
guard let containerToDropFrom = getContainer(collectionView: collectionView, sectionIndex: indexPath.section) else {
return []
}
viewModel.prepareToDropFrom(index: indexPath.row,
containerProperties: containerToDropFrom)
let itemProvider = NSItemProvider(item: stringData as NSData, typeIdentifier: "WorksheetCell" as String)
let dragItem = UIDragItem(itemProvider: itemProvider)
session.localContext = (indexPath, collectionView)
return [dragItem]
}
public func collectionView(_ collectionView: UICollectionView, dragSessionDidEnd session: UIDragSession) {
viewModel.isUserDraggingResource = false
collectionView.endInteractiveMovement()
}
}
We see this crash in production and test on physical devices. I have yet to see it on the simulator, however under similar circumstances the simulator has simply crashed with no info in Xcode. When that happens, the following stack trace is presented by Mac OS.
Translated Report (Full Report Below)
OS Version: macOS 13.0 (22A380)
Release Type: User
Report Version: 104Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Triggered by Thread: 0Last Exception Backtrace:
0 CoreFoundation 0x10fa668bb __exceptionPreprocess + 226
1 libobjc.A.dylib 0x10d49fba3 objc_exception_throw + 48
2 Foundation 0x11164137c _userInfoForFileAndLine + 0
3 UIKitCore 0x124f3c65e -[_UICollectionViewDragAndDropController _beginDragAndDropInsertingItemAtIndexPath:] + 639
4 UIKitCore 0x124f3a699 -[_UICollectionViewDragAndDropController beginReorderingForItemAtIndexPath:cell:] + 281
5 UIKitCore 0x124eee108 -[UICollectionView _beginInteractiveMovementForItemAtIndexPath:] + 263
6 UIKitCore 0x124f479c9 -[_UICollectionViewDragDestinationController _reorderingDisplayLinkDidTick] + 1545
7 QuartzCore 0x10f4b200f CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 923
8 QuartzCore 0x10f5d7aa9 display_timer_callback(__CFMachPort*, void*, long, void*) + 395
9 CoreFoundation 0x10f991c9f __CFMachPortPerform + 151
10 CoreFoundation 0x10f9c6844 CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION + 41
11 CoreFoundation 0x10f9c5d8b __CFRunLoopDoSource1 + 538
12 CoreFoundation 0x10f9c0588 __CFRunLoopRun + 2740
13 CoreFoundation 0x10f9bf6f7 CFRunLoopRunSpecific + 560
14 GraphicsServices 0x11659228a GSEventRunModal + 139
15 UIKitCore 0x1259b162b -[UIApplication _run] + 994
16 UIKitCore 0x1259b6547 UIApplicationMain + 123
17 IncidentIntelligenceSystem 0x100a568ff main + 63 (AppDelegate.swift:23)
18 dyld_sim 0x10ad082bf start_sim + 10
19 dyld 0x202a52310 start + 2432Kernel Triage:
VM - pmap_enter retried due to resource shortage
VM - pmap_enter retried due to resource shortage
VM - pmap_enter retried due to resource shortage
VM - pmap_enter retried due to resource shortage
VM - pmap_enter retried due to resource shortage
Any help would be greatly appreciated.
Running into a weird issue with TabViews not rerendering the view when objectWillChange.send() is called (either manually or with @Published). For context, in my real project I have a tab with form data and the adjacent tab is a summary tab which renders a few elements from the form data. The summary tab is not getting updated when the form data changes.
I have created a simple demo project that demonstrates the issue. The project can be found here.
The content view is just a tab view with four tabs, all of which point to the same core data object.
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@State private var selectedIndex: Int = 0
var tabTitles: Array<String> = ["Tab 1", "Tab 2", "Tab 3", "Tab 4"]
var body: some View {
// Create a page style tab view from the tab titles.
TabView(selection: $selectedIndex) {
ForEach(tabTitles.indices, id: \.self) { index in
TextView(viewModel: TextViewModel(
title: tabTitles[index],
context: viewContext))
}
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
}
}
The text view just contains the title and a text field for updating the core data object in the view model.
struct TextView: View {
@ObservedObject private var viewModel: TextViewModel
@State private var text: String
private var relay = PassthroughSubject<String, Never>()
private var debouncedPublisher: AnyPublisher<String, Never>
init(viewModel: TextViewModel) {
self.viewModel = viewModel
self._text = State(initialValue: viewModel.textValue)
self.debouncedPublisher = relay
.debounce(for: 1, scheduler: DispatchQueue.main)
.eraseToAnyPublisher()
}
var body: some View {
LazyVStack {
Text(viewModel.title)
.font(.title)
TextField("write something", text: $text)
.onChange(of: text) {
relay.send($0)
}
}
.padding()
.onReceive(debouncedPublisher) {
viewModel.textValue = $0
}
/// Without this the view does not update, with it the view updates properly...
.onAppear {
self.text = viewModel.textValue
}
}
}
And the view model is pretty simple. I've tried making item @Published, I've tried making the text value computed (with @Published item), etc. This example uses a combine publisher on item's value attribute which is a lot like what I'm doing in the main project.
class TextViewModel: ObservableObject {
@Published var textValue: String {
didSet {
// Check that the new value is not the same as the current item.value or else an infinte loop is created.
if item.value != textValue {
item.value = textValue
try! context.save()
}
}
}
private(set) var item: Item
private(set) var title: String
private var subscriber: AnyCancellable?
private var context: NSManagedObjectContext
init(title: String, context: NSManagedObjectContext) {
self.title = title
self.context = context
let request = NSFetchRequest<Item>(entityName: "Item")
request.predicate = NSPredicate(format: "%K == %@", #keyPath(Item.key), "key")
request.sortDescriptors = [NSSortDescriptor(key: #keyPath(Item.value), ascending: true)]
let fetched = try! context.fetch(request)
let fetchedItem = fetched.first!
self.textValue = fetchedItem.value!
self.item = fetchedItem
// Create a publisher to update the text value whenever the value is updated.
self.subscriber = fetchedItem.publisher(for: \.value)
.sink(receiveValue: {
if let newValue = $0 {
self.textValue = newValue
}
})
}
}
Item is just a simple core data property with a key: String and value: String.
I know I can directly bind the view to to the text value using $viewModel.textValue. It doesn't update the view when the value changes either and I don't want that behavior in my real app for a variety of reasons.
Is there something that I am missing here? Do I really need to call onAppear for all of my views within the TabView to check and see if the value is up-to-date and update it if needed? It seems a bit silly to me.
I haven't really found much info out there on this. I've also tried forcing a redraw using the (super yucky) use of @State var redraw: Bool and toggling it in onAppear. That does not trigger a redraw either.
The other thing I've tried that works is setting an @State isSelected: Bool on the TextView and in the ForEach setting it to index == selectedIndex. This works and may be the least revolting solution I have found.
Thoughts?
I merged two branches with different feature sets. I branched from one of the two then merged in the second (using command line and file merge). Now in Xcode's source control pane it shows two different branches as current. It's not a big deal but I would like to know how to get rid of the top one (at the very least) and preferably to understand why both would be displayed as current. It makes pushing to remotes a bit of a headache because they have the same app name and so I need to make sure I don't push to the wrong branch. I don't particularly want to have to redo things a different way since the branches had a fair number of merge conflicts.
Also, if I use Xcode to commit, a bunch of extra files ending with .orig come up as well as many .pbxproj files that I cannot find when I search my file directory.