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