Skip to content

Commit

Permalink
Isolate the Document UICollectionView to try to prevent access crashes.
Browse files Browse the repository at this point in the history
Fixes #207
  • Loading branch information
vincode-io committed Jan 6, 2024
1 parent 7998908 commit f51124f
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 48 deletions.
40 changes: 36 additions & 4 deletions Zavala.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,15 @@
51E060EE25A4B5E600194066 /* AppKitWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E060ED25A4B5E600194066 /* AppKitWrapper.swift */; };
51E14F23255F2D7F00320EDB /* ActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E14F22255F2D7F00320EDB /* ActivityManager.swift */; };
51E14F342560732F00320EDB /* CollectionsItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E14F332560732F00320EDB /* CollectionsItem.swift */; };
51E14F382560738E00320EDB /* UpdateItemSelectionOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E14F372560738E00320EDB /* UpdateItemSelectionOperation.swift */; };
51E14F382560738E00320EDB /* SelectItemsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E14F372560738E00320EDB /* SelectItemsOperation.swift */; };
51E14F3C256073D100320EDB /* ApplySnapshotOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E14F3B256073D100320EDB /* ApplySnapshotOperation.swift */; };
51E2BCBD290EFB15002BA7B2 /* AppKitImageAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5162AFF928EFB5C00002A61C /* AppKitImageAssets.swift */; };
51E508DB2B49E6870041B826 /* ReloadIndexPathsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E508DA2B49E6870041B826 /* ReloadIndexPathsOperation.swift */; };
51E508DD2B49EF390041B826 /* SelectIndexPathsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E508DC2B49EF390041B826 /* SelectIndexPathsOperation.swift */; };
51E508DF2B49F1590041B826 /* ReloadAllOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E508DE2B49F1590041B826 /* ReloadAllOperation.swift */; };
51E508E12B49F3450041B826 /* ApplyDiffOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E508E02B49F3450041B826 /* ApplyDiffOperation.swift */; };
51E508E32B49F5B20041B826 /* ScrollToIndexPathOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E508E22B49F5B20041B826 /* ScrollToIndexPathOperation.swift */; };
51E508E52B49F83A0041B826 /* ReconfigureIndexPathsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E508E42B49F83A0041B826 /* ReconfigureIndexPathsOperation.swift */; };
51E5C48F27221F6200A38983 /* IntentOutline+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E5C48E27221F6200A38983 /* IntentOutline+.swift */; };
51E5C49127221F6F00A38983 /* IntentRow+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E5C49027221F6F00A38983 /* IntentRow+.swift */; };
51EB162F270F46780079F40C /* ZavalaIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EB162E270F46780079F40C /* ZavalaIntentHandler.swift */; };
Expand Down Expand Up @@ -381,8 +387,14 @@
51E060F325A4C13B00194066 /* AppKitPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppKitPlugin.h; sourceTree = "<group>"; };
51E14F22255F2D7F00320EDB /* ActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityManager.swift; sourceTree = "<group>"; };
51E14F332560732F00320EDB /* CollectionsItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionsItem.swift; sourceTree = "<group>"; };
51E14F372560738E00320EDB /* UpdateItemSelectionOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateItemSelectionOperation.swift; sourceTree = "<group>"; };
51E14F372560738E00320EDB /* SelectItemsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectItemsOperation.swift; sourceTree = "<group>"; };
51E14F3B256073D100320EDB /* ApplySnapshotOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplySnapshotOperation.swift; sourceTree = "<group>"; };
51E508DA2B49E6870041B826 /* ReloadIndexPathsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReloadIndexPathsOperation.swift; sourceTree = "<group>"; };
51E508DC2B49EF390041B826 /* SelectIndexPathsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectIndexPathsOperation.swift; sourceTree = "<group>"; };
51E508DE2B49F1590041B826 /* ReloadAllOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReloadAllOperation.swift; sourceTree = "<group>"; };
51E508E02B49F3450041B826 /* ApplyDiffOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplyDiffOperation.swift; sourceTree = "<group>"; };
51E508E22B49F5B20041B826 /* ScrollToIndexPathOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollToIndexPathOperation.swift; sourceTree = "<group>"; };
51E508E42B49F83A0041B826 /* ReconfigureIndexPathsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReconfigureIndexPathsOperation.swift; sourceTree = "<group>"; };
51E5C48E27221F6200A38983 /* IntentOutline+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IntentOutline+.swift"; sourceTree = "<group>"; };
51E5C49027221F6F00A38983 /* IntentRow+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IntentRow+.swift"; sourceTree = "<group>"; };
51EB162E270F46780079F40C /* ZavalaIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZavalaIntentHandler.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -783,6 +795,7 @@
51C4C441255A0DE1004E0206 /* DocumentsViewController.swift */,
5147D0B425CB95B4004673C3 /* DocumentsViewController+Drag.swift */,
5147D0B725CCAB2A004673C3 /* DocumentsViewController+Drop.swift */,
51E508D92B49E0030041B826 /* Operations */,
);
path = Documents;
sourceTree = "<group>";
Expand Down Expand Up @@ -812,7 +825,20 @@
51E14F3B256073D100320EDB /* ApplySnapshotOperation.swift */,
51C030AB257E0A7200609262 /* ReloadItemsOperation.swift */,
51F83B852725BFF30015FC06 /* ReloadVisibleItemsOperation.swift */,
51E14F372560738E00320EDB /* UpdateItemSelectionOperation.swift */,
51E14F372560738E00320EDB /* SelectItemsOperation.swift */,
);
path = Operations;
sourceTree = "<group>";
};
51E508D92B49E0030041B826 /* Operations */ = {
isa = PBXGroup;
children = (
51E508E02B49F3450041B826 /* ApplyDiffOperation.swift */,
51E508E42B49F83A0041B826 /* ReconfigureIndexPathsOperation.swift */,
51E508DE2B49F1590041B826 /* ReloadAllOperation.swift */,
51E508DA2B49E6870041B826 /* ReloadIndexPathsOperation.swift */,
51E508E22B49F5B20041B826 /* ScrollToIndexPathOperation.swift */,
51E508DC2B49EF390041B826 /* SelectIndexPathsOperation.swift */,
);
path = Operations;
sourceTree = "<group>";
Expand Down Expand Up @@ -1057,22 +1083,26 @@
5147D0B825CCAB2A004673C3 /* DocumentsViewController+Drop.swift in Sources */,
51C030AC257E0A7200609262 /* ReloadItemsOperation.swift in Sources */,
514F222A25C4FF02004E02CD /* EditorTagInputTextField.swift in Sources */,
51E508DB2B49E6870041B826 /* ReloadIndexPathsOperation.swift in Sources */,
5147D0B525CB95B4004673C3 /* DocumentsViewController+Drag.swift in Sources */,
51077C5227A752DF000C71DB /* UIFont+.swift in Sources */,
51FA273A2B3B6493008B665E /* SettingsAccountsView.swift in Sources */,
51D977FA257A2E4F00D98490 /* EditorRowPreviewParameters.swift in Sources */,
5191850927137DFD00B7E177 /* GetCurrentTagsIntentHandler.swift in Sources */,
51E508DF2B49F1590041B826 /* ReloadAllOperation.swift in Sources */,
517AF69C25E0862E0044DBAB /* AppDefaults.swift in Sources */,
517A9E1B2602ACE000EF346A /* OutlineEditorSceneDelegate.swift in Sources */,
51F2859B25AD12800019C573 /* CollectionsSearchContentView.swift in Sources */,
51FA27462B3B9337008B665E /* SettingsHelpView.swift in Sources */,
51C030B8257E198B00609262 /* EditorTitleTextView.swift in Sources */,
51F13EFE255E13A9004B85CA /* ZavalaImageAssets.swift in Sources */,
510471BD2719AEFA001DEFD5 /* RemoveRowsIntentHandler.swift in Sources */,
51E508E32B49F5B20041B826 /* ScrollToIndexPathOperation.swift in Sources */,
5109BC50292579A30022E90B /* CopyDocumentLinkActivity.swift in Sources */,
51028CD525F3159A00B50B71 /* UIColor+.swift in Sources */,
512D375225F06733000FA597 /* EditorSearchBar.swift in Sources */,
51F2859425AD11100019C573 /* CollectionsSearchCell.swift in Sources */,
51E508E52B49F83A0041B826 /* ReconfigureIndexPathsOperation.swift in Sources */,
51DFDC18259EA8D300C068C6 /* Selector+.swift in Sources */,
515F164B255C9F8E00FD98B8 /* MacFormViewController.swift in Sources */,
51EB162F270F46780079F40C /* ZavalaIntentHandler.swift in Sources */,
Expand All @@ -1099,6 +1129,7 @@
51918515271664FB00B7E177 /* AddRowsIntentHandler.swift in Sources */,
5115C0362605F96100416D67 /* MacOpenQuicklyDocumentsViewController.swift in Sources */,
511EC2C72B409A5B002D89A5 /* ValueStepper.swift in Sources */,
51E508DD2B49EF390041B826 /* SelectIndexPathsOperation.swift in Sources */,
5142C295260AC78C0094571F /* OutlineFontConfig.swift in Sources */,
51C71C78273FFA6700F6C779 /* EditorRowContentView.swift in Sources */,
517A9E432605558700EF346A /* MacOpenQuicklyViewController.swift in Sources */,
Expand All @@ -1107,7 +1138,7 @@
51A1EC422723246C00463A15 /* EditRowsIntentHandler.swift in Sources */,
5181A93C2554D92000C153E8 /* CollectionsViewController.swift in Sources */,
51D460FA2729E30C0024EC48 /* MetadataTextAttachmentViewProvider.swift in Sources */,
51E14F382560738E00320EDB /* UpdateItemSelectionOperation.swift in Sources */,
51E14F382560738E00320EDB /* SelectItemsOperation.swift in Sources */,
51AEEC8D2703E6EB00354C59 /* Intents.intentdefinition in Sources */,
516F5373260829060038BAF8 /* OutlineFontDefaults.swift in Sources */,
51341E04272B55080005425B /* InsetLabel.swift in Sources */,
Expand All @@ -1123,6 +1154,7 @@
51DEE82826FF951E006DAA56 /* UIWindowSceneDelegate+.swift in Sources */,
51C4C442255A0DE1004E0206 /* DocumentsViewController.swift in Sources */,
511EC2C12B3F5AA7002D89A5 /* SettingsViewController.swift in Sources */,
51E508E12B49F3450041B826 /* ApplyDiffOperation.swift in Sources */,
511EC2C52B3F8F29002D89A5 /* SettingsAdvancedView.swift in Sources */,
5181A9382554D92000C153E8 /* AppDelegate.swift in Sources */,
514BB9E7255B609600C8DBDF /* UIStoryboard+.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion Zavala/Collections/CollectionsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ extension CollectionsViewController {

func updateSelections(_ containers: [DocumentContainer]?, isNavigationBranch: Bool, animated: Bool, completion: (() -> Void)?) {
let items = containers?.map { CollectionsItem.item($0) } ?? [CollectionsItem]()
collectionViewQueue.add(UpdateItemSelectionOperation(dataSource: dataSource, collectionView: collectionView, items: items, animated: animated))
collectionViewQueue.add(SelectItemsOperation(dataSource: dataSource, collectionView: collectionView, items: items, animated: animated))

let containers = items.toContainers()
delegate?.documentContainerSelectionsDidChange(self, documentContainers: containers, isNavigationBranch: isNavigationBranch, animated: animated, completion: completion)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// UpdateItemSelectionOperation.swift
// SelectItemsOperation.swift
// Zavala
//
// Created by Maurice Parker on 11/14/20.
Expand All @@ -8,7 +8,7 @@
import UIKit
import VinUtility

class UpdateItemSelectionOperation<S: Hashable, I: Hashable>: BaseMainThreadOperation {
class SelectItemsOperation<S: Hashable, I: Hashable>: BaseMainThreadOperation {

private var dataSource: UICollectionViewDiffableDataSource<S, I>
private var collectionView: UICollectionView
Expand Down
77 changes: 36 additions & 41 deletions Zavala/Documents/DocumentsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class DocumentsViewController: UICollectionViewController, MainControllerIdentif
collectionView.dropDelegate = self
collectionView.remembersLastFocusedIndexPath = true
collectionView.collectionViewLayout = createLayout()
collectionView.reloadData()
collectionViewQueue.add(ReloadAllOperation(collectionView: collectionView))

rowRegistration = UICollectionView.CellRegistration<ConsistentCollectionViewListCell, Document> { [weak self] (cell, indexPath, document) in
guard let self else { return }
Expand Down Expand Up @@ -148,7 +148,7 @@ class DocumentsViewController: UICollectionViewController, MainControllerIdentif
func updateContainer() {
self.documentContainers = documentContainers
updateUI()
collectionView.deselectAll()
collectionViewQueue.add(SelectIndexPathsOperation(collectionView: collectionView, at: [], scrollPosition: [], animated: true))
loadDocuments(animated: false, isNavigationBranch: isNavigationBranch, completion: completion)
}

Expand All @@ -166,10 +166,11 @@ class DocumentsViewController: UICollectionViewController, MainControllerIdentif
func selectDocument(_ document: Document?, isNew: Bool = false, isNavigationBranch: Bool = true, animated: Bool) {
guard let documentContainers else { return }

collectionView.deselectAll()
collectionViewQueue.add(SelectIndexPathsOperation(collectionView: collectionView, at: [], scrollPosition: [], animated: false))

if let document = document, let index = documents.firstIndex(of: document) {
collectionView.selectItem(at: IndexPath(row: index, section: 0), animated: true, scrollPosition: .centeredVertically)
let indexPath = IndexPath(row: index, section: 0)
collectionViewQueue.add(SelectIndexPathsOperation(collectionView: collectionView, at: [indexPath], scrollPosition: .centeredVertically, animated: false))
delegate?.documentSelectionDidChange(self, documentContainers: documentContainers, documents: [document], isNew: isNew, isNavigationBranch: isNavigationBranch, animated: animated)
} else {
delegate?.documentSelectionDidChange(self, documentContainers: documentContainers, documents: [], isNew: isNew, isNavigationBranch: isNavigationBranch, animated: animated)
Expand All @@ -179,9 +180,12 @@ class DocumentsViewController: UICollectionViewController, MainControllerIdentif
func selectAllDocuments() {
guard let documentContainers else { return }

var indexPaths = [IndexPath]()
for i in 0..<collectionView.numberOfItems(inSection: 0) {
collectionView.selectItem(at: IndexPath(row: i, section: 0), animated: false, scrollPosition: [])
indexPaths.append(IndexPath(row: i, section: 0))
}
collectionViewQueue.add(SelectIndexPathsOperation(collectionView: collectionView, at: indexPaths, scrollPosition: [], animated: false))


delegate?.documentSelectionDidChange(self, documentContainers: documentContainers, documents: documents, isNew: false, isNavigationBranch: false, animated: true)
}
Expand Down Expand Up @@ -310,7 +314,7 @@ extension DocumentsViewController {
}

if !(collectionView.indexPathsForSelectedItems?.contains(indexPath) ?? false) {
collectionView.deselectAll()
collectionViewQueue.add(SelectIndexPathsOperation(collectionView: collectionView, at: [], scrollPosition: [], animated: true))
}

let allRowIDs: [GenericRowIdentifier]
Expand Down Expand Up @@ -380,7 +384,7 @@ extension DocumentsViewController {
}

func openDocumentInNewWindow(indexPath: IndexPath) {
collectionView.deselectAll()
collectionViewQueue.add(SelectIndexPathsOperation(collectionView: collectionView, at: [], scrollPosition: [], animated: true))
let document = documents[indexPath.row]

let activity = NSUserActivity(activityType: NSUserActivity.ActivityType.openEditor)
Expand All @@ -391,10 +395,10 @@ extension DocumentsViewController {
func reload(document: Document) {
let selectedIndexPaths = self.collectionView.indexPathsForSelectedItems
if let index = documents.firstIndex(of: document) {
collectionView.reloadItems(at: [IndexPath(row: index, section: 0)])
collectionViewQueue.add(ReloadIndexPathsOperation(collectionView: collectionView, at: [IndexPath(row: index, section: 0)]))
}
if let selectedItem = selectedIndexPaths?.first {
collectionView.selectItem(at: selectedItem, animated: false, scrollPosition: [])
if let selectedIndexPaths {
collectionViewQueue.add(SelectIndexPathsOperation(collectionView: collectionView, at:selectedIndexPaths, scrollPosition: [], animated: false))
}
}

Expand Down Expand Up @@ -425,56 +429,47 @@ extension DocumentsViewController {
}
}

group.notify(queue: DispatchQueue.main) {
group.notify(queue: DispatchQueue.main) {
let sortedDocuments = documents.sorted(by: { ($0.title ?? "").caseInsensitiveCompare($1.title ?? "") == .orderedAscending })

guard animated else {
self.documents = sortedDocuments
self.collectionView.reloadData()
self.delegate?.documentSelectionDidChange(self, documentContainers: documentContainers, documents: [], isNew: false, isNavigationBranch: isNavigationBranch, animated: true)
self.collectionViewQueue.add(ReloadAllOperation(collectionView: self.collectionView))
self.delegate?.documentSelectionDidChange(self,
documentContainers: documentContainers,
documents: [],
isNew: false,
isNavigationBranch: isNavigationBranch,
animated: true)
completion?()
return
}

let prevSelectedDoc = self.collectionView.indexPathsForSelectedItems?.map({ self.documents[$0.row] }).first

let diff = sortedDocuments.difference(from: self.documents).inferringMoves()
self.documents = sortedDocuments

self.collectionView.performBatchUpdates {
for change in diff {
switch change {
case .insert(let offset, _, let associated):
if let associated {
self.collectionView.moveItem(at: IndexPath(row: associated, section: 0), to: IndexPath(row: offset, section: 0))
} else {
self.collectionView.insertItems(at: [IndexPath(row: offset, section: 0)])
}
case .remove(let offset, _, let associated):
if let associated {
self.collectionView.moveItem(at: IndexPath(row: offset, section: 0), to: IndexPath(row: associated, section: 0))
} else {
self.collectionView.deleteItems(at: [IndexPath(row: offset, section: 0)])
}
}
}
}

if let prevSelectedDoc = prevSelectedDoc, let index = self.documents.firstIndex(of: prevSelectedDoc) {
self.collectionViewQueue.add(ApplyDiffOperation(collectionView: self.collectionView, oldDocuments: Array(self.documents), newDocuments: sortedDocuments))
self.documents = sortedDocuments

if let prevSelectedDoc = prevSelectedDoc, let index = sortedDocuments.firstIndex(of: prevSelectedDoc) {
let indexPath = IndexPath(row: index, section: 0)
self.collectionView.selectItem(at: indexPath, animated: false, scrollPosition: [])
self.collectionView.scrollToItem(at: indexPath, at: [], animated: true)
self.collectionViewQueue.add(SelectIndexPathsOperation(collectionView: self.collectionView, at: [indexPath], scrollPosition: [], animated: false))
self.collectionViewQueue.add(ScrollToIndexPathOperation(collectionView: self.collectionView, at: indexPath, scrollPosition: [], animated: true))
} else {
self.delegate?.documentSelectionDidChange(self, documentContainers: documentContainers, documents: [], isNew: false, isNavigationBranch: isNavigationBranch, animated: true)
self.delegate?.documentSelectionDidChange(self,
documentContainers: documentContainers,
documents: [],
isNew: false,
isNavigationBranch: isNavigationBranch,
animated: true)
}

completion?()
}
}

private func reconfigureAll() {
let indexPaths = (0..<documents.count).map { IndexPath(row: $0, section: 0) }
collectionView.reconfigureItems(at: indexPaths)
collectionViewQueue.add(ReconfigureIndexPathsOperation(collectionView: collectionView, indexPaths: indexPaths))
}

private func scheduleReconfigureAll() {
Expand Down
Loading

0 comments on commit f51124f

Please sign in to comment.