Fix crash when sort and filter change at the same time.
Fix edit table cell during reload
This commit is contained in:
@@ -1111,7 +1111,7 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = main/main.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 19;
|
||||
CURRENT_PROJECT_VERSION = 20;
|
||||
INFOPLIST_FILE = main/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -1130,7 +1130,7 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = main/main.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 19;
|
||||
CURRENT_PROJECT_VERSION = 20;
|
||||
INFOPLIST_FILE = main/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -1149,7 +1149,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 19;
|
||||
CURRENT_PROJECT_VERSION = 20;
|
||||
INFOPLIST_FILE = GlassVPN/Info.plist;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN";
|
||||
@@ -1167,7 +1167,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 19;
|
||||
CURRENT_PROJECT_VERSION = 20;
|
||||
INFOPLIST_FILE = GlassVPN/Info.plist;
|
||||
MARKETING_VERSION = 1.0.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN";
|
||||
|
||||
@@ -1,24 +1,15 @@
|
||||
import UIKit
|
||||
|
||||
protocol FilterPipelineDelegate: AnyObject {
|
||||
/// Call `reloadData()` on main thread.
|
||||
/// - Warning: This function may be called from a background thread.
|
||||
/// Call `reloadData()`
|
||||
func filterPipelineDidReset()
|
||||
|
||||
/// Call `safeDeleteRows()` on main thread.
|
||||
/// - Warning: This function may be called from a background thread.
|
||||
/// Call `safeDeleteRows()`
|
||||
func filterPipeline(delete rows: [Int])
|
||||
|
||||
/// Call `safeInsertRow()` on main thread.
|
||||
/// - Warning: This function may be called from a background thread.
|
||||
/// Call `safeInsertRow()`
|
||||
func filterPipeline(insert row: Int)
|
||||
|
||||
/// Call `safeReloadRow()` on main thread.
|
||||
/// - Warning: This function may be called from a background thread.
|
||||
/// Call `safeReloadRow()`
|
||||
func filterPipeline(update row: Int)
|
||||
|
||||
/// Call `safeMoveRow()` on main thread.
|
||||
/// - Warning: This function may be called from a background thread.
|
||||
/// Call `safeMoveRow()`
|
||||
func filterPipeline(move oldRow: Int, to newRow: Int)
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,9 @@ extension GroupedDomainDataSource {
|
||||
for (i, val) in logs.enumerated() {
|
||||
logs[i].options = DomainFilter[val.domain]
|
||||
}
|
||||
pipeline.reset(dataSource: logs)
|
||||
DispatchQueue.main.sync {
|
||||
pipeline.reset(dataSource: logs)
|
||||
}
|
||||
}
|
||||
|
||||
func syncUpdate(_: SyncUpdate, insert rows: SQLiteRowRange, affects: SyncUpdateEnd) {
|
||||
@@ -109,17 +111,19 @@ extension GroupedDomainDataSource {
|
||||
assertionFailure("NotifySyncInsert fired with empty range")
|
||||
return
|
||||
}
|
||||
cellAnimationsGroup(if: latest.count > 14)
|
||||
for x in latest {
|
||||
if let (i, obj) = pipeline.dataSourceGet(where: { $0.domain == x.domain }) {
|
||||
pipeline.update(obj + x, at: i)
|
||||
} else {
|
||||
var y = x
|
||||
y.options = DomainFilter[x.domain]
|
||||
pipeline.addNew(y)
|
||||
DispatchQueue.main.sync {
|
||||
cellAnimationsGroup(if: latest.count > 14)
|
||||
for x in latest {
|
||||
if let (i, obj) = pipeline.dataSourceGet(where: { $0.domain == x.domain }) {
|
||||
pipeline.update(obj + x, at: i)
|
||||
} else {
|
||||
var y = x
|
||||
y.options = DomainFilter[x.domain]
|
||||
pipeline.addNew(y)
|
||||
}
|
||||
}
|
||||
cellAnimationsCommit()
|
||||
}
|
||||
cellAnimationsCommit()
|
||||
}
|
||||
|
||||
func syncUpdate(_ sender: SyncUpdate, remove rows: SQLiteRowRange, affects: SyncUpdateEnd) {
|
||||
@@ -132,21 +136,23 @@ extension GroupedDomainDataSource {
|
||||
outdated.count > 0 else {
|
||||
return
|
||||
}
|
||||
cellAnimationsGroup(if: outdated.count > 14)
|
||||
var listOfDeletes: [Int] = []
|
||||
for x in outdated {
|
||||
guard let (i, obj) = pipeline.dataSourceGet(where: { $0.domain == x.domain }) else {
|
||||
assertionFailure("Try to remove non-existent element")
|
||||
continue // should never happen
|
||||
}
|
||||
if obj.total > x.total {
|
||||
pipeline.update(obj - x, at: i)
|
||||
} else {
|
||||
listOfDeletes.append(i)
|
||||
DispatchQueue.main.sync {
|
||||
cellAnimationsGroup(if: outdated.count > 14)
|
||||
var listOfDeletes: [Int] = []
|
||||
for x in outdated {
|
||||
guard let (i, obj) = pipeline.dataSourceGet(where: { $0.domain == x.domain }) else {
|
||||
assertionFailure("Try to remove non-existent element")
|
||||
continue // should never happen
|
||||
}
|
||||
if obj.total > x.total {
|
||||
pipeline.update(obj - x, at: i)
|
||||
} else {
|
||||
listOfDeletes.append(i)
|
||||
}
|
||||
}
|
||||
pipeline.remove(indices: listOfDeletes.sorted())
|
||||
cellAnimationsCommit()
|
||||
}
|
||||
pipeline.remove(indices: listOfDeletes.sorted())
|
||||
cellAnimationsCommit()
|
||||
}
|
||||
|
||||
func syncUpdate(_ sender: SyncUpdate, partialRemove affectedFQDN: String) {
|
||||
@@ -155,17 +161,19 @@ extension GroupedDomainDataSource {
|
||||
return // does not affect current table
|
||||
}
|
||||
let affected = (parent == nil ? affectedParent : affectedFQDN)
|
||||
guard let old = pipeline.dataSourceGet(where: { $0.domain == affected }) else {
|
||||
// can only happen if delete sheet is open while background sync removed the element
|
||||
return
|
||||
}
|
||||
if var updated = AppDB?.dnsLogsGrouped(range: sender.rows, matchingDomain: affected,
|
||||
parentDomain: parent)?.first {
|
||||
assert(old.object.domain == updated.domain)
|
||||
updated.options = DomainFilter[updated.domain]
|
||||
pipeline.update(updated, at: old.index)
|
||||
} else {
|
||||
pipeline.remove(indices: [old.index])
|
||||
let updated = AppDB?.dnsLogsGrouped(range: sender.rows, matchingDomain: affected, parentDomain: parent)?.first
|
||||
DispatchQueue.main.sync {
|
||||
guard let old = pipeline.dataSourceGet(where: { $0.domain == affected }) else {
|
||||
// can only happen if delete sheet is open while background sync removed the element
|
||||
return
|
||||
}
|
||||
if var updated = updated {
|
||||
assert(old.object.domain == updated.domain)
|
||||
updated.options = DomainFilter[updated.domain]
|
||||
pipeline.update(updated, at: old.index)
|
||||
} else {
|
||||
pipeline.remove(indices: [old.index])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,9 +188,8 @@ extension GroupedDomainDataSource {
|
||||
extension GroupedDomainDataSource {
|
||||
/// Sets `pipeline.delegate = nil` to disable individual cell animations (update, insert, delete & move).
|
||||
private func cellAnimationsGroup(if condition: Bool = true) {
|
||||
if condition { pipeline.delegate = nil }
|
||||
if pipeline.delegate != nil {
|
||||
onMain { if !$0.isFrontmost { self.pipeline.delegate = nil } }
|
||||
if condition || delegate?.tableView.isFrontmost == false {
|
||||
pipeline.delegate = nil
|
||||
}
|
||||
}
|
||||
/// No-Op if cell animations are enabled already.
|
||||
@@ -190,33 +197,26 @@ extension GroupedDomainDataSource {
|
||||
private func cellAnimationsCommit() {
|
||||
if pipeline.delegate == nil {
|
||||
pipeline.delegate = self
|
||||
onMain { $0.reloadData() }
|
||||
}
|
||||
}
|
||||
/// Perform table view manipulations on main thread.
|
||||
/// Set `delegate = nil` to disable `tableView` animations.
|
||||
private func onMain(_ closure: (UITableView) -> Void) {
|
||||
if Thread.isMainThread {
|
||||
if let tv = delegate?.tableView { closure(tv) }
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
if let tv = delegate?.tableView { closure(tv) }
|
||||
}
|
||||
delegate?.tableView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Collect animations and post them in a single animations block.
|
||||
// This will require enormous work to translate then into a final set.
|
||||
func filterPipelineDidReset() { onMain { $0.reloadData() } }
|
||||
func filterPipeline(delete rows: [Int]) { onMain { $0.safeDeleteRows(rows) } }
|
||||
func filterPipeline(insert row: Int) { onMain { $0.safeInsertRow(row, with: .left) } }
|
||||
func filterPipeline(update row: Int) { onMain { $0.safeReloadRow(row) } }
|
||||
// This will require enormous work to translate them into a final set.
|
||||
func filterPipelineDidReset() { delegate?.tableView.reloadData() }
|
||||
func filterPipeline(delete rows: [Int]) { delegate?.tableView.safeDeleteRows(rows) }
|
||||
func filterPipeline(insert row: Int) { delegate?.tableView.safeInsertRow(row, with: .left) }
|
||||
func filterPipeline(update row: Int) {
|
||||
guard let tv = delegate?.tableView else { return }
|
||||
if !tv.isEditing { tv.safeReloadRow(row) }
|
||||
else if tv.isFrontmost == true {
|
||||
delegate?.groupedDomainDataSource(needsUpdate: row)
|
||||
}
|
||||
}
|
||||
func filterPipeline(move oldRow: Int, to newRow: Int) {
|
||||
onMain {
|
||||
$0.safeMoveRow(oldRow, to: newRow)
|
||||
if $0.isFrontmost { // onMain ensures delegate is set
|
||||
delegate!.groupedDomainDataSource(needsUpdate: newRow)
|
||||
}
|
||||
delegate?.tableView.safeMoveRow(oldRow, to: newRow)
|
||||
if delegate?.tableView.isFrontmost == true {
|
||||
delegate?.groupedDomainDataSource(needsUpdate: newRow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user