diff --git a/apps/ios/Snowbound/App.swift b/apps/ios/Snowbound/App.swift index f57d97bd9dfea1494d190cbd06a39e52ee411bfb..3c21ff5aa88f80e86df32343509298732ece7102 100644 --- a/apps/ios/Snowbound/App.swift +++ b/apps/ios/Snowbound/App.swift @@ -168,7 +168,6 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate, UISplitViewContro private let split = RootViewController(style: .tripleColumn) private let notebooks = NotebooksViewController() private let pages = PagesViewController() - private var showingPage = false /// A first launch opened a page by itself. private var landed = false private var background = UIBackgroundTaskIdentifier.invalid @@ -288,12 +287,14 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate, UISplitViewContro controller.present(navigation, animated: true) } - func show(section: Section, page: String, reveal: Reveal? = nil, titleFocus: Bool = false) { - showingPage = true + func show(section: Section, page: String, reveal: Reveal? = nil, titleFocus: Bool = false, revealPage: Bool = true) { let controller = PageViewController(section: section, page: page, reveal: reveal, titleFocus: titleFocus) - controller.onOpen = { [weak self] id in self?.show(section: section, page: id) } + controller.onOpen = { [weak self, weak controller] id in + guard let controller else { return } + self?.show(section: section, page: id, revealPage: controller.viewIfLoaded?.window != nil) + } split.setViewController(UINavigationController(rootViewController: controller), for: .secondary) - split.show(.secondary) + if revealPage { split.show(.secondary) } pages.select(page) Place.saved = Place(notebook: section.notebook.id, section: section.tab.path, page: page) } @@ -302,7 +303,6 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate, UISplitViewContro func close(_ notebook: Notebook) { guard pages.section?.notebook === notebook else { return } pages.clear() - showingPage = false split.setViewController(UIViewController(), for: .secondary) split.show(.primary) } @@ -335,7 +335,8 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate, UISplitViewContro func splitViewController( _ svc: UISplitViewController, topColumnForCollapsingToProposedTopColumn proposedTopColumn: UISplitViewController.Column ) -> UISplitViewController.Column { - showingPage ? proposedTopColumn : .primary + let detail = (svc.viewController(for: .secondary) as? UINavigationController)?.topViewController + return detail is PageViewController ? proposedTopColumn : .primary } /// Composition ends and every edit is stored and published while the system allows. diff --git a/apps/ios/Snowbound/Arranging.swift b/apps/ios/Snowbound/Arranging.swift index f43914b9ecdae7aac1e35483f1d90ed2a76cc718..9737172fa2b803f04206b6593c95da85721f3a4a 100644 --- a/apps/ios/Snowbound/Arranging.swift +++ b/apps/ios/Snowbound/Arranging.swift @@ -261,6 +261,7 @@ extension PagesViewController: UITableViewDragDelegate, UITableViewDropDelegate tableView.dragDelegate = self tableView.dropDelegate = self tableView.dragInteractionEnabled = true + tableView.allowsMultipleSelectionDuringEditing = true navigationItem.rightBarButtonItem = editButtonItem } @@ -395,12 +396,6 @@ extension PagesViewController: UITableViewDragDelegate, UITableViewDropDelegate DispatchQueue.main.async { [weak self] in self?.move(item.id, to: index) } } - override func tableView( - _ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath - ) -> UITableViewCell.EditingStyle { - tableView.isEditing ? .none : .delete - } - override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool { false } @@ -453,6 +448,7 @@ extension NotebooksViewController: UICollectionViewDragDelegate, UICollectionVie collectionView.dragDelegate = self collectionView.dropDelegate = self collectionView.dragInteractionEnabled = true + collectionView.allowsMultipleSelectionDuringEditing = true // A sidebar's trailing edge has no room left for it. if UIDevice.current.userInterfaceIdiom == .pad { navigationItem.leftBarButtonItem = editButtonItem @@ -460,7 +456,9 @@ extension NotebooksViewController: UICollectionViewDragDelegate, UICollectionVie navigationItem.rightBarButtonItems?.append(editButtonItem) } // Rows dragged between rows, and by Edit's reorder handles, move through the data source. - dataSource.reorderingHandlers.canReorderItem = { [weak self] item in self?.movable(item) == true } + dataSource.reorderingHandlers.canReorderItem = { [weak self] item in + self?.movable(item) == true && (self?.collectionView.indexPathsForSelectedItems?.count ?? 0) < 2 + } // The data source is still applying the move when this runs. dataSource.reorderingHandlers.didReorder = { [weak self] transaction in DispatchQueue.main.async { self?.reordered(transaction) } @@ -477,12 +475,6 @@ extension NotebooksViewController: UICollectionViewDragDelegate, UICollectionVie becomeFirstResponder() } - override func setEditing(_ editing: Bool, animated: Bool) { - super.setEditing(editing, animated: animated) - collectionView.isEditing = editing - collectionView.dragInteractionEnabled = !editing - } - private func movable(_ item: Item) -> Bool { switch item { case .notebook, .group, .section: true @@ -553,7 +545,9 @@ extension NotebooksViewController: UICollectionViewDragDelegate, UICollectionVie /// Adds Edit's reorder handle and VoiceOver's ways to move to a row. func arranging(_ cell: UICollectionViewListCell, _ item: Item) { - if movable(item) { cell.accessories.append(.reorder(displayed: .whenEditing)) } + if movable(item) { + cell.accessories += [.multiselect(), .reorder(displayed: .whenEditing)] + } var actions: [UIAccessibilityCustomAction] = [] let add = { (name: String, act: @escaping () -> Void) in actions.append(UIAccessibilityCustomAction(name: name) { _ in act(); return true }) diff --git a/apps/ios/Snowbound/Lists.swift b/apps/ios/Snowbound/Lists.swift index b8e471e34cf843e6ace355b5863ce24da99a2bc6..8fbc3d7569139fbe76ba2452e1f1bcf285bc00f6 100644 --- a/apps/ios/Snowbound/Lists.swift +++ b/apps/ios/Snowbound/Lists.swift @@ -47,7 +47,7 @@ final class NotebooksViewController: UICollectionViewController, UIDocumentPicke var list = UICollectionLayoutListConfiguration(appearance: compact ? .insetGrouped : .sidebar) list.headerMode = .firstItemInSection list.trailingSwipeActionsConfigurationProvider = { indexPath in - guard let self, let item = self.dataSource.itemIdentifier(for: indexPath) else { return nil } + guard let self, !self.isEditing, let item = self.dataSource.itemIdentifier(for: indexPath) else { return nil } if case .notebook(let id) = item, let notebook = self.notebook(id) { return UISwipeActionsConfiguration(actions: self.swipeActions(for: notebook, at: indexPath)) } @@ -141,14 +141,11 @@ final class NotebooksViewController: UICollectionViewController, UIDocumentPicke ] navigationItem.searchController = SearchViewController.controller() setUpArranging() - let corner = Prototype.quickNote ? [QuickNote.item()] : [] if #available(iOS 26, *) { // Search sits in the bottom toolbar, as Notes and Files put it. navigationItem.preferredSearchBarPlacement = .integrated - toolbarItems = [navigationItem.searchBarPlacementBarButtonItem] + (corner.isEmpty ? [] : [.fixedSpace()] + corner) - } else { - toolbarItems = corner.isEmpty ? [] : [.flexibleSpace()] + corner } + updateSelectionActions() for name in [Sync.changed, Section.changed] { NotificationCenter.default.addObserver(self, selector: #selector(refreshSync), name: name, object: nil) } @@ -172,6 +169,7 @@ final class NotebooksViewController: UICollectionViewController, UIDocumentPicke func reload(animated: Bool = false) { guard isViewLoaded else { return } + let selection = collectionView.indexPathsForSelectedItems?.compactMap { dataSource.itemIdentifier(for: $0) } ?? [] if Prototype.welcome { let welcome = Prototype.welcome( new: { [weak self] in self?.newNotebook(inCloud: ICloud.documents != nil) }, @@ -230,6 +228,14 @@ final class NotebooksViewController: UICollectionViewController, UIDocumentPicke dataSource.apply(list, to: location, animatingDifferences: animated) } showSelection() + if isEditing { + for item in selection { + if let index = dataSource.indexPath(for: item) { + collectionView.selectItem(at: index, animated: false, scrollPosition: []) + } + } + updateSelectionActions() + } refreshSync() } @@ -294,10 +300,142 @@ final class NotebooksViewController: UICollectionViewController, UIDocumentPicke /// Marks the open section, as the sidebar of a wide window keeps it. private func showSelection() { - guard let selected, let indexPath = dataSource.indexPath(for: selected) else { return } + guard !isEditing, let selected, let indexPath = dataSource.indexPath(for: selected) else { return } collectionView.selectItem(at: indexPath, animated: false, scrollPosition: []) } + override func setEditing(_ editing: Bool, animated: Bool) { + super.setEditing(editing, animated: animated) + for index in collectionView.indexPathsForSelectedItems ?? [] { + collectionView.deselectItem(at: index, animated: false) + } + collectionView.isEditing = editing + collectionView.dragInteractionEnabled = !editing + if !editing { showSelection() } + updateSelectionActions() + } + + private func updateSelectionActions() { + guard isEditing else { + let corner = Prototype.quickNote ? [QuickNote.item()] : [] + if #available(iOS 26, *) { + toolbarItems = [navigationItem.searchBarPlacementBarButtonItem] + (corner.isEmpty ? [] : [.fixedSpace()] + corner) + } else { + toolbarItems = corner.isEmpty ? [] : [.flexibleSpace()] + corner + } + navigationController?.setToolbarHidden(toolbarItems?.isEmpty != false, animated: false) + return + } + let selection = (collectionView.indexPathsForSelectedItems ?? []).sorted().compactMap { + dataSource.itemIdentifier(for: $0) + } + var actions: [UIMenuElement] = [] + let notebooks = selection.compactMap { item -> Notebook? in + guard case .notebook(let id) = item else { return nil } + return notebook(id) + } + if !selection.isEmpty, notebooks.count == selection.count { + let folders = notebooks.compactMap { folder(of: $0) } + if folders.count == notebooks.count { + actions.append(UIAction(title: "Share", image: UIImage(systemName: "square.and.arrow.up")) { [weak self] _ in + guard let self else { return } + let sheet = UIActivityViewController(activityItems: folders, applicationActivities: nil) + sheet.popoverPresentationController?.barButtonItem = toolbarItems?.last + present(sheet, animated: true) + }) + let deletable = notebooks.allSatisfy { $0.handle != nil } + actions.append(UIAction(title: "Delete", image: UIImage(systemName: "trash"), attributes: deletable ? .destructive : [.destructive, .disabled]) { [weak self] _ in + self?.delete(notebooks) + }) + } else if notebooks.allSatisfy({ notebook in Notebooks.elsewhere.contains { $0 === notebook } }) { + actions.append(UIAction(title: "Close", image: UIImage(systemName: "xmark.circle")) { [weak self] _ in + for notebook in notebooks { + for scene in UIApplication.shared.connectedScenes { + (scene.delegate as? SceneDelegate)?.close(notebook) + } + Notebooks.remove(notebook) + } + self?.reload() + }) + } + } + let entries = selection.compactMap { item -> (Notebook, String)? in + switch item { + case .section(let id, let path), .group(let id, let path): return notebook(id).map { ($0, path) } + default: return nil + } + } + if let notebook = entries.first?.0, entries.count == selection.count, + entries.allSatisfy({ $0.0 === notebook }) + { + let paths = entries.map(\.1).filter { path in !entries.contains { path.hasPrefix($0.1 + "/") } } + let destinations = notebook.destinations(of: paths[0]).filter { destination in + paths.allSatisfy { notebook.destinations(of: $0).contains(destination) } + } + if !destinations.isEmpty { + actions.append(UIMenu(title: "Move", image: UIImage(systemName: "folder"), children: destinations.map { folder in + UIAction(title: folder.isEmpty ? notebook.name : folder.split(separator: "/").joined(separator: " › ")) { [weak self] _ in + self?.setEditing(false, animated: true) + for path in paths { Arranging.place(path, of: notebook, in: folder) } + } + })) + } + } + let action = UIBarButtonItem(title: "Actions", image: UIImage(systemName: "ellipsis.circle"), menu: UIMenu(children: actions)) + action.isEnabled = !actions.isEmpty + toolbarItems = [.flexibleSpace(), action] + navigationController?.setToolbarHidden(false, animated: false) + } + + private func delete(_ notebooks: [Notebook]) { + let alert = UIAlertController( + title: "Delete \(notebooks.count) Notebooks?", + message: "Notebooks that can’t be moved to Recently Deleted will be deleted permanently.", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) + alert.addAction(UIAlertAction(title: "Delete", style: .destructive) { [weak self] _ in + guard let self else { return } + setEditing(false, animated: true) + collectionView.isUserInteractionEnabled = false + for notebook in notebooks { + for scene in UIApplication.shared.connectedScenes { + (scene.delegate as? SceneDelegate)?.close(notebook) + } + } + background({ () -> (String?, [Notebook]) in + // No folder is removed until every notebook has published its edits. + for notebook in notebooks { + guard let handle = notebook.handle else { + return ("\(notebook.name): Open this notebook before deleting it.", notebooks) + } + var error: UnsafeMutablePointer? + let closed = sb_library_close(handle, 10, &error) + let problem = take(error) + if !closed { return ("\(notebook.name): \(problem ?? "Some changes haven’t been saved yet.")", notebooks) } + } + var failed: [Notebook] = [] + var problems: [String] = [] + for notebook in notebooks { + guard let folder = self.folder(of: notebook) else { continue } + do { + do { try FileManager.default.trashItem(at: folder, resultingItemURL: nil) } + catch { try FileManager.default.removeItem(at: folder) } + } catch { + failed.append(notebook) + problems.append("\(notebook.name): \(error.localizedDescription)") + } + } + return (problems.isEmpty ? nil : problems.joined(separator: "\n\n"), failed) + }) { [weak self] problem, failed in + self?.collectionView.isUserInteractionEnabled = true + for notebook in notebooks { notebook.pause() } + for notebook in failed { notebook.open { self?.reload() } } + self?.rescan() + if let problem { self?.alert("Can’t Delete", problem) } + } + }) + present(alert, animated: true) + } + private func configure(_ cell: UICollectionViewListCell, _ item: Item) { var content = cell.defaultContentConfiguration() var accessories: [UICellAccessory] = [] @@ -589,7 +727,7 @@ final class NotebooksViewController: UICollectionViewController, UIDocumentPicke override func collectionView( _ collectionView: UICollectionView, contextMenuConfigurationForItemsAt indexPaths: [IndexPath], point: CGPoint ) -> UIContextMenuConfiguration? { - guard indexPaths.count == 1, let item = dataSource.itemIdentifier(for: indexPaths[0]) else { return nil } + guard !isEditing, indexPaths.count == 1, let item = dataSource.itemIdentifier(for: indexPaths[0]) else { return nil } guard case .notebook(let id) = item, let notebook = notebook(id) else { let moves = arrangeMenu(item) return moves.isEmpty ? nil : UIContextMenuConfiguration(actionProvider: { _ in UIMenu(children: moves) }) @@ -601,6 +739,12 @@ final class NotebooksViewController: UICollectionViewController, UIDocumentPicke override func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { guard let item = dataSource.itemIdentifier(for: indexPath) else { return false } + if isEditing { + switch item { + case .notebook, .group, .section: return true + default: return false + } + } switch item { case .section(let id, let path): let tab = notebook(id)?.tabs.first(where: { $0.path == path }) @@ -631,6 +775,7 @@ final class NotebooksViewController: UICollectionViewController, UIDocumentPicke } override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + if isEditing { return updateSelectionActions() } guard case .section(let id, let path) = dataSource.itemIdentifier(for: indexPath), let notebook = notebook(id), let tab = notebook.tabs.first(where: { $0.path == path }) else { return } @@ -638,6 +783,10 @@ final class NotebooksViewController: UICollectionViewController, UIDocumentPicke onOpen?(tab, notebook) } + override func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { + if isEditing { updateSelectionActions() } + } + /// Asks for a protected section's password, as OneNote's Protected Section dialog does, /// and opens it once unlocked. private func unlock(_ tab: Tab, of notebook: Notebook, wrong: Bool) { @@ -846,15 +995,10 @@ final class PagesViewController: UITableViewController { tableView.register(UITableViewCell.self, forCellReuseIdentifier: "page") navigationItem.searchController = search navigationItem.hidesSearchBarWhenScrolling = false - let add = UIBarButtonItem( - title: "New Page", image: UIImage(systemName: "plus"), - primaryAction: UIAction { [weak self] _ in self?.newPage(under: nil) }) if #available(iOS 26, *) { navigationItem.preferredSearchBarPlacement = .integrated - toolbarItems = [navigationItem.searchBarPlacementBarButtonItem, .fixedSpace(), add] - } else { - toolbarItems = [.flexibleSpace(), add] } + updateSelectionActions() NotificationCenter.default.addObserver( self, selector: #selector(changed), name: Section.changed, object: nil) setUpArranging() @@ -885,6 +1029,7 @@ final class PagesViewController: UITableViewController { } func loading(_ tab: Tab) { + if isEditing { setEditing(false, animated: false) } section = nil items = [] title = tab.name @@ -901,6 +1046,7 @@ final class PagesViewController: UITableViewController { /// Lets go of the section shown, as when its notebook goes. func clear() { + if isEditing { setEditing(false, animated: false) } section = nil items = [] title = nil @@ -929,6 +1075,7 @@ final class PagesViewController: UITableViewController { func select(_ id: String) { selected = id + guard !isEditing else { return } if let index = items.firstIndex(where: { $0.id == id }) { tableView.selectRow(at: IndexPath(row: index, section: 0), animated: false, scrollPosition: .none) } @@ -939,12 +1086,72 @@ final class PagesViewController: UITableViewController { DispatchQueue.main.async { self.search.searchBar.becomeFirstResponder() } } + override func setEditing(_ editing: Bool, animated: Bool) { + super.setEditing(editing, animated: animated) + for index in tableView.indexPathsForSelectedRows ?? [] { tableView.deselectRow(at: index, animated: false) } + tableView.dragInteractionEnabled = !editing + if !editing, let selected { select(selected) } + updateSelectionActions() + } + + private func updateSelectionActions() { + guard isEditing else { + let add = UIBarButtonItem(title: "New Page", image: UIImage(systemName: "plus"), primaryAction: UIAction { [weak self] _ in + self?.newPage(under: nil) + }) + if #available(iOS 26, *) { + toolbarItems = [navigationItem.searchBarPlacementBarButtonItem, .fixedSpace(), add] + } else { toolbarItems = [.flexibleSpace(), add] } + return + } + let selection = (tableView.indexPathsForSelectedRows ?? []).sorted().map { items[$0.row] } + let delete = UIBarButtonItem(title: "Delete", image: UIImage(systemName: "trash"), primaryAction: UIAction { [weak self] _ in + guard let self, let section else { return } + let remove = { [weak self] in + self?.setEditing(false, animated: true) + let ids = selection.filter { item in + item.version == nil || !selection.contains { $0.version == nil && $0.id == item.row.id } + }.map(\.id) + section.delete(ids) + } + if selection.contains(where: { $0.version != nil }) { + let alert = UIAlertController(title: "Delete \(selection.count) Pages?", message: "Conflict pages will be deleted permanently.", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) + alert.addAction(UIAlertAction(title: "Delete", style: .destructive) { _ in remove() }) + present(alert, animated: true) + } else { remove() } + }) + delete.tintColor = .systemRed + delete.isEnabled = !selection.isEmpty + var destinations: [UIAction] = [] + if let section, !selection.isEmpty, selection.allSatisfy({ $0.version == nil }) { + destinations = section.notebook.sections(besides: section.tab.path).map { tab in + UIAction(title: tab.name) { [weak self] _ in + self?.setEditing(false, animated: true) + for item in selection { + Arranging.move(page: item.id, of: section.notebook, from: section.tab.path, to: tab.path) + } + } + } + } + let move = UIBarButtonItem(title: "Move to Section", image: UIImage(systemName: "folder"), menu: UIMenu(children: destinations)) + move.isEnabled = !destinations.isEmpty + toolbarItems = [move, .flexibleSpace(), delete] + navigationController?.setToolbarHidden(false, animated: false) + } + private func reload() { + let selection = tableView.indexPathsForSelectedRows?.map { items[$0.row].id } ?? [] items = (section?.rows ?? []).flatMap { row in [Item(row: row, version: nil)] + row.versions.map { Item(row: row, version: $0) } } tableView.reloadData() - if let selected { select(selected) } + if isEditing { + for (index, item) in items.enumerated() where selection.contains(item.id) { + tableView.selectRow(at: IndexPath(row: index, section: 0), animated: false, scrollPosition: .none) + } + updateSelectionActions() + } else if let selected { select(selected) } var empty = UIContentUnavailableConfiguration.empty() empty.text = "No Pages" contentUnavailableConfiguration = section != nil && items.isEmpty ? empty : nil @@ -961,7 +1168,7 @@ final class PagesViewController: UITableViewController { } private func delete(_ item: Item) { - section?.delete(item.id) + section?.delete([item.id]) } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { items.count } @@ -997,16 +1204,21 @@ final class PagesViewController: UITableViewController { } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if isEditing { return updateSelectionActions() } guard let section else { return } selected = items[indexPath.row].id onOpen?(section, items[indexPath.row].id) } + override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { + if isEditing { updateSelectionActions() } + } + override func tableView( _ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath ) -> UISwipeActionsConfiguration? { // A swipe across the list changes section instead; the context menu deletes. - if Prototype.sectionStrip { return nil } + if isEditing || Prototype.sectionStrip { return nil } let item = items[indexPath.row] let delete = UIContextualAction(style: .destructive, title: "Delete") { [weak self] _, _, done in self?.delete(item) @@ -1019,6 +1231,7 @@ final class PagesViewController: UITableViewController { override func tableView( _ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint ) -> UIContextMenuConfiguration? { + guard !isEditing else { return nil } let item = items[indexPath.row] return UIContextMenuConfiguration(actionProvider: { [weak self] _ in var actions: [UIMenuElement] = [] diff --git a/apps/ios/Snowbound/Notebooks.swift b/apps/ios/Snowbound/Notebooks.swift index 2d468a4463f41c3d9826762dd5bc93abd9942798..e143d7bb860ef5111177138ad16cef89c8f68006 100644 --- a/apps/ios/Snowbound/Notebooks.swift +++ b/apps/ios/Snowbound/Notebooks.swift @@ -723,13 +723,22 @@ final class Section { return id } - func delete(_ id: String, done: @escaping () -> Void = {}) { + func delete(_ ids: [String], done: @escaping () -> Void = {}) { let (date, time) = titleDate() let pointer = Int(bitPattern: handle) - background({ sb_section_delete_page(OpaquePointer(bitPattern: pointer), id, date, time) }) { [self] _ in + background({ + for id in ids { + guard sb_section_delete_page(OpaquePointer(bitPattern: pointer), id, date, time) else { return false } + } + return true + }) { [self] deleted in reloadRows() NotificationCenter.default.post(name: Self.changed, object: self, userInfo: ["flags": Self.listed]) - done() + if deleted { done() } + else { + let window = UIApplication.shared.connectedScenes.lazy.compactMap { ($0 as? UIWindowScene)?.keyWindow }.first + window?.rootViewController?.alert("Can’t Delete", "Some pages couldn’t be deleted. Try again.") + } } } diff --git a/apps/ios/Snowbound/PageViewController.swift b/apps/ios/Snowbound/PageViewController.swift index 9b74022d5c8e89423d3e36b4498821ca6208c0e7..05ac8ead8d67cbe2bab35bf585b59ae5833618f8 100644 --- a/apps/ios/Snowbound/PageViewController.swift +++ b/apps/ios/Snowbound/PageViewController.swift @@ -225,7 +225,7 @@ final class PageViewController: UIViewController, PHPickerViewControllerDelegate UIAction(title: "Delete Page", image: UIImage(systemName: "trash"), attributes: .destructive) { [weak self] _ in guard let self else { return } - section.delete(page) + section.delete([page]) }, ]), ] @@ -464,7 +464,7 @@ final class PageViewController: UIViewController, PHPickerViewControllerDelegate private func conflictAction() { guard let (row, version) = section.row(of: page) else { return } if version != nil { - section.delete(page) { [weak self] in self?.onOpen?(row.id) } + section.delete([page]) { [weak self] in self?.onOpen?(row.id) } } else if let newest = row.versions.max(by: { ($0.created ?? 0) < ($1.created ?? 0) }) { onOpen?(newest.id) }