diff --git a/AppCheck.xcodeproj/project.pbxproj b/AppCheck.xcodeproj/project.pbxproj index 1993c61..1aef16d 100644 --- a/AppCheck.xcodeproj/project.pbxproj +++ b/AppCheck.xcodeproj/project.pbxproj @@ -135,6 +135,8 @@ 54E540F4247D3F2600F7C34A /* TestDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E540F3247D3F2600F7C34A /* TestDataSource.swift */; }; 54E540F8247DB90F00F7C34A /* RecordingsDB.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E540F7247DB90F00F7C34A /* RecordingsDB.swift */; }; 54E540FA2482414800F7C34A /* SyncUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54E540F92482414800F7C34A /* SyncUpdate.swift */; }; + 54EFA4E6248EEE240022D618 /* DatePickerAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54EFA4E5248EEE240022D618 /* DatePickerAlert.swift */; }; + 54EFA4E82491A16A0022D618 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54EFA4E72491A16A0022D618 /* Font.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -295,6 +297,8 @@ 54E540F3247D3F2600F7C34A /* TestDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestDataSource.swift; sourceTree = ""; }; 54E540F7247DB90F00F7C34A /* RecordingsDB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordingsDB.swift; sourceTree = ""; }; 54E540F92482414800F7C34A /* SyncUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncUpdate.swift; sourceTree = ""; }; + 54EFA4E5248EEE240022D618 /* DatePickerAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerAlert.swift; sourceTree = ""; }; + 54EFA4E72491A16A0022D618 /* Font.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -418,6 +422,7 @@ 545DDDCE243E6267003B6544 /* TutorialSheet.swift */, 54D8B979246C9F2000EB2414 /* FilterPipeline.swift */, 54448A3124899A4000771C96 /* SearchBarManager.swift */, + 54EFA4E5248EEE240022D618 /* DatePickerAlert.swift */, ); path = "Common Classes"; sourceTree = ""; @@ -442,6 +447,7 @@ 54B345AA241BBA5B004C53CC /* AlertSheet.swift */, 54448A2F248647D900771C96 /* Time.swift */, 54751E502423955000168273 /* URL.swift */, + 54EFA4E72491A16A0022D618 /* Font.swift */, 54448A2D2486464F00771C96 /* Array.swift */, 54D8B97B2471A7E000EB2414 /* String.swift */, 54B34595240F0513004C53CC /* TableView.swift */, @@ -837,6 +843,7 @@ 54D8B98624796E9900EB2414 /* GroupedDomainDataSource.swift in Sources */, 54953E6F23E44CD00054345C /* TVCHostDetails.swift in Sources */, 54D8B97C2471A7E000EB2414 /* String.swift in Sources */, + 54EFA4E6248EEE240022D618 /* DatePickerAlert.swift in Sources */, 54C056DD23E9EEF700214A3F /* BundleIcon.swift in Sources */, 542E2A982404973F001462DC /* TBCMain.swift in Sources */, 54448A3224899A4000771C96 /* SearchBarManager.swift in Sources */, @@ -845,6 +852,7 @@ 545DDDD124436983003B6544 /* QuickUI.swift in Sources */, 541AC5D82399498A00A769D7 /* AppDelegate.swift in Sources */, 540E67822433483D00871BBE /* VCEditRecording.swift in Sources */, + 54EFA4E82491A16A0022D618 /* Font.swift in Sources */, 54D8B97A246C9F2000EB2414 /* FilterPipeline.swift in Sources */, 54B34594240E6343004C53CC /* TVCFilter.swift in Sources */, 54E540FA2482414800F7C34A /* SyncUpdate.swift in Sources */, diff --git a/main/Common Classes/DatePickerAlert.swift b/main/Common Classes/DatePickerAlert.swift new file mode 100644 index 0000000..2f5055e --- /dev/null +++ b/main/Common Classes/DatePickerAlert.swift @@ -0,0 +1,91 @@ +import UIKit + +class DatePickerAlert: UIViewController { + + private var callback: (Date) -> Void + private let picker: UIDatePicker = { + let x = UIDatePicker() + let h = x.sizeThatFits(.zero).height + x.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: h) + return x + }() + + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + @discardableResult required init(presentIn viewController: UIViewController, configure: ((UIDatePicker) -> Void)? = nil, onSuccess: @escaping (Date) -> Void) { + callback = onSuccess + super.init(nibName: nil, bundle: nil) + modalPresentationStyle = .custom + if #available(iOS 13.0, *) { + isModalInPresentation = true + } + presentIn(viewController, configure) + } + + internal override func loadView() { + let cancel = QuickUI.button("Cancel", target: self, action: #selector(didTapCancel)) + let save = QuickUI.button("Save", target: self, action: #selector(didTapSave)) + save.titleLabel?.font = save.titleLabel?.font.bold() + //cancel.setTitleColor(.systemRed, for: .normal) + + let buttons = UIStackView(arrangedSubviews: [cancel, save]) + buttons.axis = .horizontal + buttons.distribution = .fillEqually + + let bg = UIView(frame: picker.frame) + bg.frame.size.height += buttons.frame.height + 15 + bg.frame.origin.y = UIScreen.main.bounds.height - bg.frame.height - 15 + bg.backgroundColor = .sysBg + bg.addSubview(picker) + bg.addSubview(buttons) + + let clearBg = UIView() + clearBg.autoresizingMask = [.flexibleWidth, .flexibleHeight] + clearBg.addSubview(bg) + + picker.anchor([.leading, .trailing, .top], to: bg) + picker.bottomAnchor =&= buttons.topAnchor + buttons.anchor([.leading, .trailing], to: bg) + buttons.bottomAnchor =&= bg.bottomAnchor - 15 + bg.anchor([.leading, .trailing, .bottom], to: clearBg) + + view = clearBg + view.isHidden = true // otherwise picker will flash on present + } + + @objc private func didTapSave() { + dismiss(animated: true) { + self.callback(self.picker.date) + } + } + + @objc private func didTapCancel() { + dismiss(animated: true) + } + + private func presentIn(_ viewController: UIViewController, _ configure: ((UIDatePicker) -> Void)? = nil) { + viewController.present(self, animated: false) { + let control = self.view.subviews.first! + let prev = control.frame.origin.y + control.frame.origin.y += control.frame.height + self.view.isHidden = false + + configure?(self.picker) + + UIView.animate(withDuration: 0.3) { + self.view.backgroundColor = UIColor.black.withAlphaComponent(0.5) + control.frame.origin.y = prev + } + } + } + + override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { + UIView.animate(withDuration: 0.3, animations: { + let control = self.view.subviews.first! + self.view.backgroundColor = .clear + control.frame.origin.y += control.frame.height + }) { _ in + super.dismiss(animated: false, completion: completion) + } + } +} diff --git a/main/Common Classes/QuickUI.swift b/main/Common Classes/QuickUI.swift index da94df3..8abf45d 100644 --- a/main/Common Classes/QuickUI.swift +++ b/main/Common Classes/QuickUI.swift @@ -39,32 +39,3 @@ struct QuickUI { return txt } } - -extension NSMutableAttributedString { - static private var def: UIFont = .preferredFont(forTextStyle: .body) - - func normal(_ str: String, _ style: UIFont.TextStyle = .body) -> Self { append(str, withFont: .preferredFont(forTextStyle: style)) } - func bold(_ str: String, _ style: UIFont.TextStyle = .body) -> Self { append(str, withFont: UIFont.preferredFont(forTextStyle: style).bold()) } - func italic(_ str: String, _ style: UIFont.TextStyle = .body) -> Self { append(str, withFont: UIFont.preferredFont(forTextStyle: style).italic()) } - - func h1(_ str: String) -> Self { normal(str, .title1) } - func h2(_ str: String) -> Self { normal(str, .title2) } - func h3(_ str: String) -> Self { normal(str, .title3) } - - private func append(_ str: String, withFont: UIFont) -> Self { - append(NSAttributedString(string: str, attributes: [ - .font : withFont, - .foregroundColor : UIColor.sysFg - ])) - return self - } -} - - -extension UIFont { - func withTraits(traits: UIFontDescriptor.SymbolicTraits) -> UIFont { - UIFont(descriptor: fontDescriptor.withSymbolicTraits(traits)!, size: 0) // keep size as is - } - func bold() -> UIFont { withTraits(traits: .traitBold) } - func italic() -> UIFont { withTraits(traits: .traitItalic) } -} diff --git a/main/Common Classes/TutorialSheet.swift b/main/Common Classes/TutorialSheet.swift index 1177725..845920a 100644 --- a/main/Common Classes/TutorialSheet.swift +++ b/main/Common Classes/TutorialSheet.swift @@ -47,7 +47,6 @@ class TutorialSheet: UIViewController, UIScrollViewDelegate { let content = UIView() x.addSubview(content) - content.translatesAutoresizingMaskIntoConstraints = false content.anchor([.left, .right, .top, .bottom], to: x) content.anchor([.width, .height], to: x) | .defaultLow return x @@ -62,7 +61,7 @@ class TutorialSheet: UIViewController, UIScrollViewDelegate { // MARK: Init - required init?(coder: NSCoder) { super.init(coder: coder) } + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } required init() { super.init(nibName: nil, bundle: nil) @@ -98,7 +97,6 @@ class TutorialSheet: UIViewController, UIScrollViewDelegate { pager.numberOfPages += 1 updateButtonTitle() let x = UIStackView(frame: pageScroll.bounds) - x.translatesAutoresizingMaskIntoConstraints = false x.axis = .vertical x.backgroundColor = UIColor.black x.isOpaque = true @@ -125,8 +123,6 @@ class TutorialSheet: UIViewController, UIScrollViewDelegate { sheetBg.addSubview(pageScroll) sheetBg.addSubview(button) - for x in sheetBg.subviews { x.translatesAutoresizingMaskIntoConstraints = false } - pager.anchor([.top, .left, .right], to: sheetBg) pageScroll.topAnchor =&= pager.bottomAnchor pageScroll.anchor([.left, .right, .top, .bottom], to: sheetBg, margin: cornerRadius/2) | .defaultHigh diff --git a/main/DB/DBAppOnly.swift b/main/DB/DBAppOnly.swift index 8e191c6..62c63d5 100644 --- a/main/DB/DBAppOnly.swift +++ b/main/DB/DBAppOnly.swift @@ -130,6 +130,13 @@ extension SQLiteDatabase { // MARK: read + func dnsLogsMinDate() -> Timestamp? { + try? run(sql:"SELECT min(ts) FROM heap") { + try ifStep($0, SQLITE_ROW) + return sqlite3_column_int64($0, 0) + } + } + /// Select min and max row id with given condition `ts >= ? AND ts < ?` /// - Returns: `nil` in case no rows are matching the condition func dnsLogsRowRange(between ts: Timestamp, and ts2: Timestamp) -> SQLiteRowRange? { diff --git a/main/DB/DBExtensions.swift b/main/DB/DBExtensions.swift index 347524d..28d7d48 100644 --- a/main/DB/DBExtensions.swift +++ b/main/DB/DBExtensions.swift @@ -16,8 +16,8 @@ extension GroupedDomain { extension GroupedDomain { var detailCellText: String { get { return blocked > 0 - ? "\(lastModified.asDateTime()) — \(blocked)/\(total) blocked" - : "\(lastModified.asDateTime()) — \(total)" + ? "\(DateFormat.seconds(lastModified)) — \(blocked)/\(total) blocked" + : "\(DateFormat.seconds(lastModified)) — \(total)" } } } diff --git a/main/Extensions/AutoLayout.swift b/main/Extensions/AutoLayout.swift index 073b352..7c9b772 100644 --- a/main/Extensions/AutoLayout.swift +++ b/main/Extensions/AutoLayout.swift @@ -59,6 +59,7 @@ extension UIView { private static let inverseItem: [NSLayoutConstraint.Attribute] = [.right, .bottom, .trailing, .lastBaseline, .rightMargin, .bottomMargin, .trailingMargin] /// Create and active constraints for provided edges. Constraints will anchor the same edge on both `self` and `other`. + /// - Note: Will set `translatesAutoresizingMaskIntoConstraints = false` /// - Parameters: /// - edges: List of constraint attributes, e.g. `[.top, .bottom, .left, .right]` /// - other: Instance to bind to, e.g. `UIView` or `UILayoutGuide` @@ -66,7 +67,8 @@ extension UIView { /// - rel: Constraint relation. (Default: `.equal`) /// - Returns: List of created and active constraints @discardableResult func anchor(_ edges: [NSLayoutConstraint.Attribute], to other: Any, margin: CGFloat = 0, if rel: NSLayoutConstraint.Relation = .equal) -> [NSLayoutConstraint] { - edges.map { + translatesAutoresizingMaskIntoConstraints = false + return edges.map { let (A, B) = UIView.inverseItem.contains($0) ? (other, self) : (self, other) return NSLayoutConstraint(item: A, attribute: $0, relatedBy: rel, toItem: B, attribute: $0, multiplier: 1, constant: margin).on() } diff --git a/main/Extensions/Font.swift b/main/Extensions/Font.swift new file mode 100644 index 0000000..17c8f0f --- /dev/null +++ b/main/Extensions/Font.swift @@ -0,0 +1,34 @@ +import UIKit + +extension UIFont { + func withTraits(traits: UIFontDescriptor.SymbolicTraits) -> UIFont { + UIFont(descriptor: fontDescriptor.withSymbolicTraits(traits)!, size: 0) // keep size as is + } + func bold() -> UIFont { withTraits(traits: .traitBold) } + func italic() -> UIFont { withTraits(traits: .traitItalic) } + func monoSpace() -> UIFont { + let traits = fontDescriptor.object(forKey: .traits) as? [UIFontDescriptor.TraitKey: Any] ?? [:] + let weight = (traits[.weight] as? CGFloat) ?? UIFont.Weight.regular.rawValue + return .monospacedDigitSystemFont(ofSize: pointSize, weight: .init(rawValue: weight)) + } +} + +extension NSMutableAttributedString { + static private var def: UIFont = .preferredFont(forTextStyle: .body) + + func normal(_ str: String, _ style: UIFont.TextStyle = .body) -> Self { append(str, withFont: .preferredFont(forTextStyle: style)) } + func bold(_ str: String, _ style: UIFont.TextStyle = .body) -> Self { append(str, withFont: UIFont.preferredFont(forTextStyle: style).bold()) } + func italic(_ str: String, _ style: UIFont.TextStyle = .body) -> Self { append(str, withFont: UIFont.preferredFont(forTextStyle: style).italic()) } + + func h1(_ str: String) -> Self { normal(str, .title1) } + func h2(_ str: String) -> Self { normal(str, .title2) } + func h3(_ str: String) -> Self { normal(str, .title3) } + + private func append(_ str: String, withFont: UIFont) -> Self { + append(NSAttributedString(string: str, attributes: [ + .font : withFont, + .foregroundColor : UIColor.sysFg + ])) + return self + } +} diff --git a/main/Extensions/Time.swift b/main/Extensions/Time.swift index a740951..cf08381 100644 --- a/main/Extensions/Time.swift +++ b/main/Extensions/Time.swift @@ -1,7 +1,5 @@ import Foundation -private let dateTimeFormat = DateFormatter(withFormat: "yyyy-MM-dd HH:mm:ss") - extension DateFormatter { convenience init(withFormat: String) { self.init() @@ -9,26 +7,18 @@ extension DateFormatter { } } -extension Timestamp { - /// Time string with format `yyyy-MM-dd HH:mm:ss` - func asDateTime() -> String { - dateTimeFormat.string(from: Date.init(timeIntervalSince1970: Double(self))) - } - +extension Date { /// Convert `Timestamp` to `Date` - func toDate() -> Date { - Date(timeIntervalSince1970: Double(self)) - } - + init(_ ts: Timestamp) { self.init(timeIntervalSince1970: Double(ts)) } + /// Convert `Date` to `Timestamp` + var timestamp: Timestamp { get { Timestamp(self.timeIntervalSince1970) } } +} + +extension Timestamp { /// Current time as `Timestamp` (second accuracy) - static func now() -> Timestamp { - Timestamp(Date().timeIntervalSince1970) - } - + static func now() -> Timestamp { Date().timestamp } /// Create `Timestamp` with `now() - minutes * 60` - static func past(minutes: Int) -> Timestamp { - now() - Timestamp(minutes * 60) - } + static func past(minutes: Int) -> Timestamp { now() - Timestamp(minutes * 60) } } extension Timer { @@ -39,6 +29,24 @@ extension Timer { } } + +// MARK: - DateFormat + +enum DateFormat { + private static let _hms = DateFormatter(withFormat: "yyyy-MM-dd HH:mm:ss") + private static let _hm = DateFormatter(withFormat: "yyyy-MM-dd HH:mm") + + /// Format: `yyyy-MM-dd HH:mm:ss` + static func seconds(_ date: Date) -> String { _hms.string(from: date) } + /// Format: `yyyy-MM-dd HH:mm:ss` + static func seconds(_ ts: Timestamp) -> String { _hms.string(from: Date(ts)) } + /// Format: `yyyy-MM-dd HH:mm` + static func minutes(_ date: Date) -> String { _hm.string(from: date) } + /// Format: `yyyy-MM-dd HH:mm` + static func minutes(_ ts: Timestamp) -> String { _hm.string(from: Date(ts)) } +} + + // MARK: - TimeFormat struct TimeFormat { diff --git a/main/Recordings/TVCPreviousRecords.swift b/main/Recordings/TVCPreviousRecords.swift index 086fbff..dc43a69 100644 --- a/main/Recordings/TVCPreviousRecords.swift +++ b/main/Recordings/TVCPreviousRecords.swift @@ -69,7 +69,7 @@ class TVCPreviousRecords: UITableViewController, EditActionsRemove { let x = dataSource[indexPath.row] cell.textLabel?.text = x.title ?? x.fallbackTitle cell.textLabel?.textColor = (x.title == nil) ? .systemGray : nil - cell.detailTextLabel?.text = "at \(x.start.asDateTime()), duration: \(x.durationString ?? "?")" + cell.detailTextLabel?.text = "at \(DateFormat.seconds(x.start)), duration: \(x.durationString ?? "?")" return cell } diff --git a/main/Recordings/VCEditRecording.swift b/main/Recordings/VCEditRecording.swift index 58c18f9..9ecc571 100644 --- a/main/Recordings/VCEditRecording.swift +++ b/main/Recordings/VCEditRecording.swift @@ -16,8 +16,8 @@ class VCEditRecording: UIViewController, UITextFieldDelegate, UITextViewDelegate inputTitle.text = record.title inputNotes.text = record.notes inputDetails.text = """ - Start: \(record.start.asDateTime()) - End: \(record.stop?.asDateTime() ?? "?") + Start: \(DateFormat.seconds(record.start)) + End: \(record.stop == nil ? "?" : DateFormat.seconds(record.stop!)) Duration: \(record.durationString ?? "?") """ validateSaveButton() diff --git a/main/Recordings/VCRecordings.swift b/main/Recordings/VCRecordings.swift index b789d6a..7ded67a 100644 --- a/main/Recordings/VCRecordings.swift +++ b/main/Recordings/VCRecordings.swift @@ -12,10 +12,7 @@ class VCRecordings: UIViewController, UINavigationControllerDelegate { override func viewDidLoad() { prevRecController = (children.first as! UINavigationController) prevRecController.delegate = self - // Duplicate font attributes but set monospace - let traits = timeLabel.font.fontDescriptor.object(forKey: .traits) as? [UIFontDescriptor.TraitKey: Any] ?? [:] - let weight = traits[.weight] as? CGFloat ?? UIFont.Weight.regular.rawValue - timeLabel.font = UIFont.monospacedDigitSystemFont(ofSize: timeLabel.font.pointSize, weight: UIFont.Weight(rawValue: weight)) + timeLabel.font = timeLabel.font.monoSpace() // hide timer if not running updateUI(setRecording: false, animated: false) currentRecording = RecordingsDB.getCurrent() @@ -71,7 +68,7 @@ class VCRecordings: UIViewController, UINavigationControllerDelegate { guard let r = currentRecording, r.stop == nil else { return } - recordingTimer = Timer.repeating(0.086, call: #selector(timerCallback(_:)), on: self, userInfo: r.start.toDate()) + recordingTimer = Timer.repeating(0.086, call: #selector(timerCallback(_:)), on: self, userInfo: Date(r.start)) updateUI(setRecording: true, animated: animate) } diff --git a/main/Requests/TVCHostDetails.swift b/main/Requests/TVCHostDetails.swift index 25c2fb8..07fec53 100644 --- a/main/Requests/TVCHostDetails.swift +++ b/main/Requests/TVCHostDetails.swift @@ -67,7 +67,7 @@ class TVCHostDetails: UITableViewController { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "HostDetailCell")! let src = dataSource[indexPath.row] - cell.textLabel?.text = src.ts.asDateTime() + cell.textLabel?.text = DateFormat.seconds(src.ts) cell.detailTextLabel?.text = (src.total > 1) ? "\(src.total)x" : nil cell.imageView?.image = (src.blocked > 0 ? UIImage(named: "shield-x") : nil) return cell diff --git a/main/Requests/VCDateFilter.swift b/main/Requests/VCDateFilter.swift index b4e957f..27bd5e6 100644 --- a/main/Requests/VCDateFilter.swift +++ b/main/Requests/VCDateFilter.swift @@ -18,6 +18,8 @@ class VCDateFilter: UIViewController, UIGestureRecognizerDelegate { @IBOutlet private var rangeView: UIView! @IBOutlet private var buttonRangeStart: UIButton! @IBOutlet private var buttonRangeEnd: UIButton! + private lazy var tsRangeA: Timestamp = AppDB?.dnsLogsMinDate() ?? 0 + private lazy var tsRangeB: Timestamp = .now() // order by @IBOutlet private var orderbyType: UISegmentedControl! @@ -35,12 +37,11 @@ class VCDateFilter: UIViewController, UIGestureRecognizerDelegate { durationSlider.value = Float(durationTimes.firstIndex(of: Pref.DateFilter.LastXMin) ?? 0) / 9 durationSliderChanged(durationSlider) - var a = Timestamp(4).asDateTime() // TODO: load from preferences - var b = Timestamp.now().asDateTime() - a.removeLast(3) // remove seconds - b.removeLast(3) - buttonRangeStart.setTitle(a, for: .normal) - buttonRangeEnd.setTitle(b, for: .normal) + // Force set seconds to 00 and 59 respectively. Its retained during change. + tsRangeA = tsRangeA - tsRangeA % 60 + 00 + tsRangeB = tsRangeB - tsRangeB % 60 + 59 + buttonRangeStart.setTitle(DateFormat.minutes(tsRangeA), for: .normal) + buttonRangeEnd.setTitle(DateFormat.minutes(tsRangeB), for: .normal) orderbyType.selectedSegmentIndex = Pref.DateFilter.OrderBy.rawValue orderbyAsc.selectedSegmentIndex = (Pref.DateFilter.OrderAsc ? 0 : 1) @@ -65,7 +66,13 @@ class VCDateFilter: UIViewController, UIGestureRecognizerDelegate { } @IBAction private func didTapRangeButton(_ sender: UIButton) { - // TODO: show date picker + let flag = (sender == buttonRangeStart) + DatePickerAlert(presentIn: self, configure: { + $0.setDate(Date(flag ? self.tsRangeA : self.tsRangeB), animated: false) + }, onSuccess: { + flag ? (self.tsRangeA = $0.timestamp) : (self.tsRangeB = $0.timestamp) + sender.setTitle(DateFormat.minutes($0), for: .normal) + }) } func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { @@ -85,6 +92,7 @@ class VCDateFilter: UIViewController, UIGestureRecognizerDelegate { case 2: orderType = .Count default: preconditionFailure() } + sync.pause() let orderAsc = (orderbyAsc.selectedSegmentIndex == 0) if Pref.DateFilter.OrderBy != orderType || Pref.DateFilter.OrderAsc != orderAsc { Pref.DateFilter.OrderBy = orderType @@ -96,6 +104,7 @@ class VCDateFilter: UIViewController, UIGestureRecognizerDelegate { Pref.DateFilter.LastXMin = newXMin NotifyDateFilterChanged.post() } + sync.continue() dismiss(animated: true) } return false