Speeding up local search. Support for regular expressions.
This commit is contained in:
parent
ae99fb98a7
commit
4496149fb7
@ -12,6 +12,7 @@
|
|||||||
7A0420B12561A0E100034941 /* OsagoAddController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0420B02561A0E100034941 /* OsagoAddController.swift */; };
|
7A0420B12561A0E100034941 /* OsagoAddController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0420B02561A0E100034941 /* OsagoAddController.swift */; };
|
||||||
7A0420B62568650C00034941 /* DkbmController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0420B52568650C00034941 /* DkbmController.swift */; };
|
7A0420B62568650C00034941 /* DkbmController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0420B52568650C00034941 /* DkbmController.swift */; };
|
||||||
7A0420BA25693D2C00034941 /* dkbm.js in Resources */ = {isa = PBXBuildFile; fileRef = 7A0420B925693D2C00034941 /* dkbm.js */; };
|
7A0420BA25693D2C00034941 /* dkbm.js in Resources */ = {isa = PBXBuildFile; fileRef = 7A0420B925693D2C00034941 /* dkbm.js */; };
|
||||||
|
7A0B663729984201006F5189 /* DateCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0B663629984201006F5189 /* DateCache.swift */; };
|
||||||
7A0B96A0257D6D4B000B39AD /* MultilineLabelRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0B969F257D6D4B000B39AD /* MultilineLabelRow.swift */; };
|
7A0B96A0257D6D4B000B39AD /* MultilineLabelRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0B969F257D6D4B000B39AD /* MultilineLabelRow.swift */; };
|
||||||
7A1090E824A394F100B4F0B2 /* AudioRecordCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1090E724A394F100B4F0B2 /* AudioRecordCell.swift */; };
|
7A1090E824A394F100B4F0B2 /* AudioRecordCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1090E724A394F100B4F0B2 /* AudioRecordCell.swift */; };
|
||||||
7A1090EA24A3A26300B4F0B2 /* AudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1090E924A3A26300B4F0B2 /* AudioPlayer.swift */; };
|
7A1090EA24A3A26300B4F0B2 /* AudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1090E924A3A26300B4F0B2 /* AudioPlayer.swift */; };
|
||||||
@ -80,7 +81,6 @@
|
|||||||
7AA7BC3325A5DFB80053A5D5 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 7AF58D332402A91C00CE01A0 /* Kingfisher */; };
|
7AA7BC3325A5DFB80053A5D5 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 7AF58D332402A91C00CE01A0 /* Kingfisher */; };
|
||||||
7AA7BC3525A5DFB80053A5D5 /* ExceptionCatcher in Frameworks */ = {isa = PBXBuildFile; productRef = 7A813DC02508C4D900CC93B9 /* ExceptionCatcher */; };
|
7AA7BC3525A5DFB80053A5D5 /* ExceptionCatcher in Frameworks */ = {isa = PBXBuildFile; productRef = 7A813DC02508C4D900CC93B9 /* ExceptionCatcher */; };
|
||||||
7AA7BC3625A5DFB80053A5D5 /* PKHUD in Frameworks */ = {isa = PBXBuildFile; productRef = 7AABDE1C2532F3EB0041AFC6 /* PKHUD */; };
|
7AA7BC3625A5DFB80053A5D5 /* PKHUD in Frameworks */ = {isa = PBXBuildFile; productRef = 7AABDE1C2532F3EB0041AFC6 /* PKHUD */; };
|
||||||
7AABB1F0267E9CAA00D7AB32 /* DifferenceKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7AABB1EF267E9CAA00D7AB32 /* DifferenceKit */; settings = {ATTRIBUTES = (Required, ); }; };
|
|
||||||
7AABB1F2267E9CC800D7AB32 /* SwiftDate in Frameworks */ = {isa = PBXBuildFile; productRef = 7AABB1F1267E9CC800D7AB32 /* SwiftDate */; };
|
7AABB1F2267E9CC800D7AB32 /* SwiftDate in Frameworks */ = {isa = PBXBuildFile; productRef = 7AABB1F1267E9CC800D7AB32 /* SwiftDate */; };
|
||||||
7AABDE26253350C30041AFC6 /* RxSectionedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AABDE25253350C30041AFC6 /* RxSectionedDataSource.swift */; };
|
7AABDE26253350C30041AFC6 /* RxSectionedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AABDE25253350C30041AFC6 /* RxSectionedDataSource.swift */; };
|
||||||
7AB67E8C2435C38700258F61 /* CustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB67E8B2435C38700258F61 /* CustomTextField.swift */; };
|
7AB67E8C2435C38700258F61 /* CustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB67E8B2435C38700258F61 /* CustomTextField.swift */; };
|
||||||
@ -174,6 +174,7 @@
|
|||||||
7A0420B52568650C00034941 /* DkbmController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DkbmController.swift; sourceTree = "<group>"; };
|
7A0420B52568650C00034941 /* DkbmController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DkbmController.swift; sourceTree = "<group>"; };
|
||||||
7A0420B925693D2C00034941 /* dkbm.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = dkbm.js; sourceTree = "<group>"; };
|
7A0420B925693D2C00034941 /* dkbm.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = dkbm.js; sourceTree = "<group>"; };
|
||||||
7A0516192414FF0900FC55AC /* DateSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateSection.swift; sourceTree = "<group>"; };
|
7A0516192414FF0900FC55AC /* DateSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateSection.swift; sourceTree = "<group>"; };
|
||||||
|
7A0B663629984201006F5189 /* DateCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateCache.swift; sourceTree = "<group>"; };
|
||||||
7A0B969F257D6D4B000B39AD /* MultilineLabelRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineLabelRow.swift; sourceTree = "<group>"; };
|
7A0B969F257D6D4B000B39AD /* MultilineLabelRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineLabelRow.swift; sourceTree = "<group>"; };
|
||||||
7A1090E724A394F100B4F0B2 /* AudioRecordCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecordCell.swift; sourceTree = "<group>"; };
|
7A1090E724A394F100B4F0B2 /* AudioRecordCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecordCell.swift; sourceTree = "<group>"; };
|
||||||
7A1090E924A3A26300B4F0B2 /* AudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = "<group>"; };
|
7A1090E924A3A26300B4F0B2 /* AudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = "<group>"; };
|
||||||
@ -321,7 +322,6 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
7AA54C1E26CD977A00F2BF28 /* RxSwift in Frameworks */,
|
7AA54C1E26CD977A00F2BF28 /* RxSwift in Frameworks */,
|
||||||
7AABB1F0267E9CAA00D7AB32 /* DifferenceKit in Frameworks */,
|
|
||||||
7AF6D2252677C2B40086EA64 /* RealmSwift in Frameworks */,
|
7AF6D2252677C2B40086EA64 /* RealmSwift in Frameworks */,
|
||||||
7AA54C1C26CD977A00F2BF28 /* RxCocoa in Frameworks */,
|
7AA54C1C26CD977A00F2BF28 /* RxCocoa in Frameworks */,
|
||||||
7AF6D2232677C2B40086EA64 /* Realm in Frameworks */,
|
7AF6D2232677C2B40086EA64 /* Realm in Frameworks */,
|
||||||
@ -646,6 +646,7 @@
|
|||||||
7A11474323FF06CA00B424AF /* Api.swift */,
|
7A11474323FF06CA00B424AF /* Api.swift */,
|
||||||
7A96AE30246B2FE400297C33 /* Constants.swift */,
|
7A96AE30246B2FE400297C33 /* Constants.swift */,
|
||||||
7A000AA124C2EEDE001F5B00 /* Location.swift */,
|
7A000AA124C2EEDE001F5B00 /* Location.swift */,
|
||||||
|
7A0B663629984201006F5189 /* DateCache.swift */,
|
||||||
);
|
);
|
||||||
path = Utils;
|
path = Utils;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -733,7 +734,6 @@
|
|||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
7AF6D2222677C2B40086EA64 /* Realm */,
|
7AF6D2222677C2B40086EA64 /* Realm */,
|
||||||
7AF6D2242677C2B40086EA64 /* RealmSwift */,
|
7AF6D2242677C2B40086EA64 /* RealmSwift */,
|
||||||
7AABB1EF267E9CAA00D7AB32 /* DifferenceKit */,
|
|
||||||
7AABB1F1267E9CC800D7AB32 /* SwiftDate */,
|
7AABB1F1267E9CC800D7AB32 /* SwiftDate */,
|
||||||
7AA54C1B26CD977A00F2BF28 /* RxCocoa */,
|
7AA54C1B26CD977A00F2BF28 /* RxCocoa */,
|
||||||
7AA54C1D26CD977A00F2BF28 /* RxSwift */,
|
7AA54C1D26CD977A00F2BF28 /* RxSwift */,
|
||||||
@ -783,7 +783,6 @@
|
|||||||
7A05160F241412CA00FC55AC /* XCRemoteSwiftPackageReference "SwiftDate" */,
|
7A05160F241412CA00FC55AC /* XCRemoteSwiftPackageReference "SwiftDate" */,
|
||||||
7A813DBF2508C4D900CC93B9 /* XCRemoteSwiftPackageReference "ExceptionCatcher" */,
|
7A813DBF2508C4D900CC93B9 /* XCRemoteSwiftPackageReference "ExceptionCatcher" */,
|
||||||
7AABDE1B2532F3EB0041AFC6 /* XCRemoteSwiftPackageReference "PKHUD" */,
|
7AABDE1B2532F3EB0041AFC6 /* XCRemoteSwiftPackageReference "PKHUD" */,
|
||||||
7AABDE21253327F10041AFC6 /* XCRemoteSwiftPackageReference "DifferenceKit" */,
|
|
||||||
7A35177927E23F8800DC538C /* XCRemoteSwiftPackageReference "Eureka" */,
|
7A35177927E23F8800DC538C /* XCRemoteSwiftPackageReference "Eureka" */,
|
||||||
7AC355482969652F00889457 /* XCRemoteSwiftPackageReference "SwiftEntryKit" */,
|
7AC355482969652F00889457 /* XCRemoteSwiftPackageReference "SwiftEntryKit" */,
|
||||||
);
|
);
|
||||||
@ -941,6 +940,7 @@
|
|||||||
7A761C07267E8E7F0005F28F /* AnyEncodable.swift in Sources */,
|
7A761C07267E8E7F0005F28F /* AnyEncodable.swift in Sources */,
|
||||||
7AF6D2162677C1680086EA64 /* Cloneable.swift in Sources */,
|
7AF6D2162677C1680086EA64 /* Cloneable.swift in Sources */,
|
||||||
7AF6D21A2677C1680086EA64 /* User.swift in Sources */,
|
7AF6D21A2677C1680086EA64 /* User.swift in Sources */,
|
||||||
|
7A0B663729984201006F5189 /* DateCache.swift in Sources */,
|
||||||
7AF6D21D2677C1680086EA64 /* Osago.swift in Sources */,
|
7AF6D21D2677C1680086EA64 /* Osago.swift in Sources */,
|
||||||
7AF6D2152677C1680086EA64 /* Settings.swift in Sources */,
|
7AF6D2152677C1680086EA64 /* Settings.swift in Sources */,
|
||||||
7A761C052677F1BC0005F28F /* CocoaError.swift in Sources */,
|
7A761C052677F1BC0005F28F /* CocoaError.swift in Sources */,
|
||||||
@ -1140,7 +1140,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
|
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 110;
|
CURRENT_PROJECT_VERSION = 111;
|
||||||
DEVELOPMENT_TEAM = 46DTTB8X4S;
|
DEVELOPMENT_TEAM = 46DTTB8X4S;
|
||||||
INFOPLIST_FILE = AutoCat/Info.plist;
|
INFOPLIST_FILE = AutoCat/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
@ -1165,7 +1165,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
|
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 110;
|
CURRENT_PROJECT_VERSION = 111;
|
||||||
DEVELOPMENT_TEAM = 46DTTB8X4S;
|
DEVELOPMENT_TEAM = 46DTTB8X4S;
|
||||||
INFOPLIST_FILE = AutoCat/Info.plist;
|
INFOPLIST_FILE = AutoCat/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
@ -1381,14 +1381,6 @@
|
|||||||
minimumVersion = 5.4.0;
|
minimumVersion = 5.4.0;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
7AABDE21253327F10041AFC6 /* XCRemoteSwiftPackageReference "DifferenceKit" */ = {
|
|
||||||
isa = XCRemoteSwiftPackageReference;
|
|
||||||
repositoryURL = "https://github.com/ra1028/DifferenceKit.git";
|
|
||||||
requirement = {
|
|
||||||
kind = upToNextMajorVersion;
|
|
||||||
minimumVersion = 1.1.5;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
7AC355482969652F00889457 /* XCRemoteSwiftPackageReference "SwiftEntryKit" */ = {
|
7AC355482969652F00889457 /* XCRemoteSwiftPackageReference "SwiftEntryKit" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/huri000/SwiftEntryKit";
|
repositoryURL = "https://github.com/huri000/SwiftEntryKit";
|
||||||
@ -1433,11 +1425,6 @@
|
|||||||
package = 7A530B89240181F500CBFE6E /* XCRemoteSwiftPackageReference "RxRealm" */;
|
package = 7A530B89240181F500CBFE6E /* XCRemoteSwiftPackageReference "RxRealm" */;
|
||||||
productName = RxRealm;
|
productName = RxRealm;
|
||||||
};
|
};
|
||||||
7AABB1EF267E9CAA00D7AB32 /* DifferenceKit */ = {
|
|
||||||
isa = XCSwiftPackageProductDependency;
|
|
||||||
package = 7AABDE21253327F10041AFC6 /* XCRemoteSwiftPackageReference "DifferenceKit" */;
|
|
||||||
productName = DifferenceKit;
|
|
||||||
};
|
|
||||||
7AABB1F1267E9CC800D7AB32 /* SwiftDate */ = {
|
7AABB1F1267E9CC800D7AB32 /* SwiftDate */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 7A05160F241412CA00FC55AC /* XCRemoteSwiftPackageReference "SwiftDate" */;
|
package = 7A05160F241412CA00FC55AC /* XCRemoteSwiftPackageReference "SwiftDate" */;
|
||||||
|
|||||||
@ -1,14 +1,5 @@
|
|||||||
{
|
{
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
|
||||||
"identity" : "differencekit",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/ra1028/DifferenceKit.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "14c66681e12a38b81045f44c6c29724a0d4b0e72",
|
|
||||||
"version" : "1.1.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"identity" : "eureka",
|
"identity" : "eureka",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
|||||||
@ -294,18 +294,24 @@ class CheckController: UIViewController, UITableViewDelegate, UISearchResultsUpd
|
|||||||
// MARK: - UISearchResultsUpdating
|
// MARK: - UISearchResultsUpdating
|
||||||
|
|
||||||
func updateSearchResults(for searchController: UISearchController) {
|
func updateSearchResults(for searchController: UISearchController) {
|
||||||
guard let realm = try? Realm() else {
|
guard let text = searchController.searchBar.text?.uppercased(),
|
||||||
|
!text.isEmpty
|
||||||
|
else {
|
||||||
|
historyDataSource.setSearchPredicate(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var query = realm.objects(Vehicle.self)
|
let regex = try? NSRegularExpression(pattern: text)
|
||||||
.sorted(byKeyPath: "updatedDate", ascending: false)
|
|
||||||
|
|
||||||
if let text = searchController.searchBar.text?.uppercased(), !text.isEmpty {
|
historyDataSource.setSearchPredicate { vehicle in
|
||||||
query = query.filter("number CONTAINS '\(text)'")
|
let number = vehicle.getNumber()
|
||||||
|
if let regex {
|
||||||
|
let range = NSRange(location: 0, length: number.utf16.count)
|
||||||
|
return regex.numberOfMatches(in: number, range: range) > 0
|
||||||
|
} else {
|
||||||
|
return number.contains(text)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
historyDataSource.setQuery(query)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Contextual actions
|
// MARK: - Contextual actions
|
||||||
|
|||||||
@ -10,6 +10,13 @@ class MainTabController: UITabBarController, UITabBarControllerDelegate {
|
|||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
self.delegate = self
|
self.delegate = self
|
||||||
|
|
||||||
|
#if targetEnvironment(macCatalyst)
|
||||||
|
|
||||||
|
// Remove "+" tab for macOS version (it will be on the toolbar)
|
||||||
|
viewControllers?.remove(at: 2)
|
||||||
|
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
|
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftDate
|
import SwiftDate
|
||||||
import DifferenceKit
|
|
||||||
import AutoCatCore
|
import AutoCatCore
|
||||||
|
|
||||||
protocol Dated {
|
protocol Dated {
|
||||||
var added: Date { get }
|
var added: Date { get }
|
||||||
|
var addedTimestamp: TimeInterval { get }
|
||||||
var updated: Date { get }
|
var updated: Date { get }
|
||||||
|
var updatedTimestamp: TimeInterval { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Vehicle: Dated {
|
extension Vehicle: Dated {
|
||||||
|
|
||||||
var updated: Date {
|
var updated: Date {
|
||||||
Date(timeIntervalSince1970: self.updatedDate)
|
Date(timeIntervalSince1970: self.updatedDate)
|
||||||
}
|
}
|
||||||
@ -16,6 +18,14 @@ extension Vehicle: Dated {
|
|||||||
var added: Date {
|
var added: Date {
|
||||||
Date(timeIntervalSince1970: self.addedDate)
|
Date(timeIntervalSince1970: self.addedDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var addedTimestamp: TimeInterval {
|
||||||
|
addedDate
|
||||||
|
}
|
||||||
|
|
||||||
|
var updatedTimestamp: TimeInterval {
|
||||||
|
updatedDate
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AudioRecord: Dated {
|
extension AudioRecord: Dated {
|
||||||
@ -26,38 +36,48 @@ extension AudioRecord: Dated {
|
|||||||
var added: Date {
|
var added: Date {
|
||||||
Date(timeIntervalSince1970: self.getAddedDate())
|
Date(timeIntervalSince1970: self.getAddedDate())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var addedTimestamp: TimeInterval {
|
||||||
|
getAddedDate()
|
||||||
|
}
|
||||||
|
|
||||||
|
var updatedTimestamp: TimeInterval {
|
||||||
|
getAddedDate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension RandomAccessCollection where Element: Dated & Hashable & Differentiable {
|
extension RandomAccessCollection where Element: Dated & Identifiable {
|
||||||
func groupedByDate(type: SortParameter = .updatedDate) -> [DateSection<Element>] {
|
func groupedByDate(type: SortParameter = .updatedDate) -> [DateSection<Element>] {
|
||||||
let now = Date()
|
|
||||||
let monthStart = now.dateAtStartOf(.month)
|
|
||||||
var sectionsIndices: [TimeInterval: Int] = [:]
|
|
||||||
var sectionsArray: [DateSection<Element>] = []
|
var sectionsArray: [DateSection<Element>] = []
|
||||||
for vehicle in self {
|
var key: TimeInterval = 0
|
||||||
let date = self.date(of: type, for: vehicle)
|
var keyNext: TimeInterval = 0
|
||||||
let dateInRegion = DateInRegion(date, region: Region.current)
|
var index = self.index(before: endIndex)
|
||||||
|
let currentMonthStart = DateCache.shared.monthStart.timeIntervalSince1970
|
||||||
|
|
||||||
var key = dateInRegion.dateAtStartOf(.day).timeIntervalSince1970
|
while index >= startIndex {
|
||||||
if date.isBeforeDate(monthStart, orEqual: false, granularity: .day) {
|
|
||||||
key = dateInRegion.dateAtStartOf(.month).timeIntervalSince1970
|
let timestamp: TimeInterval
|
||||||
|
switch type {
|
||||||
|
case .addedDate: timestamp = self[index].addedTimestamp
|
||||||
|
case .updatedDate: timestamp = self[index].updatedTimestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
if let index = sectionsIndices[key] {
|
if keyNext == 0 || timestamp > keyNext {
|
||||||
sectionsArray[index].append(vehicle)
|
|
||||||
} else {
|
let component: Calendar.Component = timestamp >= currentMonthStart ? .day : .month
|
||||||
sectionsArray.append(DateSection<Element>(timestamp: key, items: [vehicle]))
|
let dateInRegion = DateInRegion(seconds: timestamp, region: .current)
|
||||||
sectionsIndices[key] = sectionsArray.count - 1
|
let startOfPeriod = dateInRegion.dateAtStartOf(component)
|
||||||
|
key = startOfPeriod.timeIntervalSince1970
|
||||||
|
|
||||||
|
sectionsArray.insert(DateSection<Element>(timestamp: key, items: []), at: 0)
|
||||||
|
keyNext = startOfPeriod.dateByAdding(1, component).timeIntervalSince1970
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sectionsArray[0].insert(self[index], at: 0)
|
||||||
|
index = self.index(before: index)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sectionsArray
|
return sectionsArray
|
||||||
}
|
}
|
||||||
|
|
||||||
func date(of type: SortParameter, for object: Dated) -> Date {
|
|
||||||
switch type {
|
|
||||||
case .addedDate: return object.added
|
|
||||||
case .updatedDate: return object.updated
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,6 +39,21 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||||||
self.window?.rootViewController = storyboard.instantiateViewController(identifier: "MainSplitController")
|
self.window?.rootViewController = storyboard.instantiateViewController(identifier: "MainSplitController")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if targetEnvironment(macCatalyst)
|
||||||
|
|
||||||
|
let toolbar = NSToolbar(identifier: "main")
|
||||||
|
toolbar.delegate = self
|
||||||
|
toolbar.displayMode = .iconOnly
|
||||||
|
|
||||||
|
if let titlebar = windowScene.titlebar {
|
||||||
|
titlebar.toolbar = toolbar
|
||||||
|
if #available(macCatalyst 14.0, *) {
|
||||||
|
titlebar.toolbarStyle = .automatic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
self.window?.makeKeyAndVisible()
|
self.window?.makeKeyAndVisible()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,3 +178,20 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if targetEnvironment(macCatalyst)
|
||||||
|
|
||||||
|
extension SceneDelegate: NSToolbarDelegate {
|
||||||
|
|
||||||
|
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||||
|
let identifiers: [NSToolbarItem.Identifier] = [
|
||||||
|
.toggleSidebar
|
||||||
|
]
|
||||||
|
return identifiers
|
||||||
|
}
|
||||||
|
|
||||||
|
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||||
|
return toolbarDefaultItemIdentifiers(toolbar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import RealmSwift
|
import RealmSwift
|
||||||
import DifferenceKit
|
|
||||||
import ExceptionCatcher
|
import ExceptionCatcher
|
||||||
import AutoCatCore
|
import AutoCatCore
|
||||||
|
|
||||||
typealias FilterPredicate<T> = (T) -> Bool
|
typealias FilterPredicate<T> = (T) -> Bool
|
||||||
|
|
||||||
class RealmSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource
|
class RealmSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource
|
||||||
where Item: Object & Identifiable & Dated & Differentiable & Cloneable,
|
where Item: Object & Identifiable & Dated & Cloneable,
|
||||||
Cell: UITableViewCell & ConfigurableCell,
|
Cell: UITableViewCell & ConfigurableCell,
|
||||||
Cell.Item == Item {
|
Cell.Item == Item {
|
||||||
|
|
||||||
@ -17,8 +16,11 @@ class RealmSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource
|
|||||||
private var sections: [DateSection<Item>] = []
|
private var sections: [DateSection<Item>] = []
|
||||||
private var cellIdentifier: String
|
private var cellIdentifier: String
|
||||||
private var filterPredicate: FilterPredicate<Item>?
|
private var filterPredicate: FilterPredicate<Item>?
|
||||||
|
private var searchPredicate: FilterPredicate<Item>?
|
||||||
private let groupQueue = DispatchQueue(label: "group")
|
private let groupQueue = DispatchQueue(label: "group")
|
||||||
|
|
||||||
|
private var objects: [Item] = []
|
||||||
|
|
||||||
private let onSizeChanged: ((Int) -> Void)?
|
private let onSizeChanged: ((Int) -> Void)?
|
||||||
|
|
||||||
init(table: UITableView, data: Results<Item>, cellIdentifier: String = String(describing: Cell.self), onSizeChanged: ((Int) -> Void)? = nil) {
|
init(table: UITableView, data: Results<Item>, cellIdentifier: String = String(describing: Cell.self), onSizeChanged: ((Int) -> Void)? = nil) {
|
||||||
@ -35,24 +37,30 @@ class RealmSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource
|
|||||||
|
|
||||||
func startObservingChanges() {
|
func startObservingChanges() {
|
||||||
self.notificationToken = self.data.observe { changes in
|
self.notificationToken = self.data.observe { changes in
|
||||||
self.onSizeChanged?(self.data.count)
|
|
||||||
switch changes {
|
switch changes {
|
||||||
case .initial:
|
case .initial:
|
||||||
|
self.objects = self.data.toArray().map { $0.shallowClone() }
|
||||||
self.reload(animated: false)
|
self.reload(animated: false)
|
||||||
case .update(_, let deletions, let insertions, let modifications):
|
case .update(_, let deletions, let insertions, let modifications):
|
||||||
if deletions.isEmpty && modifications.isEmpty && insertions.count == 1 && insertions.first == 0 {
|
|
||||||
// Most common use case - adding new element to the top of the list
|
deletions.forEach { self.objects.remove(at: $0) }
|
||||||
// Example - checking new number
|
insertions.forEach { self.objects.insert(self.data[$0].shallowClone(), at: $0) }
|
||||||
self.insertFirst()
|
modifications.forEach { self.objects[$0] = self.data[$0].shallowClone() }
|
||||||
} else if deletions.isEmpty && insertions.isEmpty && modifications.count == 1 && modifications.first == 0 {
|
self.reload()
|
||||||
self.reloadFirst()
|
|
||||||
} else if modifications.isEmpty && deletions.count == 1 && insertions.count == 1 && insertions.first == 0 {
|
// if deletions.isEmpty && modifications.isEmpty && insertions.count == 1 && insertions.first == 0 {
|
||||||
// Probably moving an item to the first position
|
// // Most common use case - adding new element to the top of the list
|
||||||
// For example - updating number info
|
// // Example - checking new number
|
||||||
self.moveToFirst(deleteIndex: deletions.first!)
|
// self.insertFirst()
|
||||||
} else {
|
// } else if deletions.isEmpty && insertions.isEmpty && modifications.count == 1 && modifications.first == 0 {
|
||||||
self.reload(animated: false)
|
// self.reloadFirst()
|
||||||
}
|
// } else if modifications.isEmpty && deletions.count == 1 && insertions.count == 1 && insertions.first == 0 {
|
||||||
|
// // Probably moving an item to the first position
|
||||||
|
// // For example - updating number info
|
||||||
|
// self.moveToFirst(deleteIndex: deletions.first!)
|
||||||
|
// } else {
|
||||||
|
// self.reload(animated: false)
|
||||||
|
// }
|
||||||
case .error(let err):
|
case .error(let err):
|
||||||
print("Realm observer error: \(err)")
|
print("Realm observer error: \(err)")
|
||||||
}
|
}
|
||||||
@ -90,26 +98,23 @@ class RealmSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource
|
|||||||
}
|
}
|
||||||
|
|
||||||
func reload(animated: Bool = true) {
|
func reload(animated: Bool = true) {
|
||||||
var items = self.data.toArray().map { $0.shallowClone() }
|
var items = objects
|
||||||
if let predicate = self.filterPredicate {
|
if let filterPredicate = self.filterPredicate {
|
||||||
items = items.filter(predicate)
|
items = items.filter(filterPredicate)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.groupQueue.async {
|
if let searchPredicate = self.searchPredicate {
|
||||||
let newSections = items.groupedByDate()
|
items = items.filter(searchPredicate)
|
||||||
// let changeset = StagedChangeset(source: self.sections, target: newSections)
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.sections = newSections
|
|
||||||
self.tv.reloadData()
|
|
||||||
// self.tv.reload(using: changeset, with: animated ? .fade : .none) { newSects in
|
|
||||||
// self.sections = newSects
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.onSizeChanged?(items.count)
|
||||||
|
|
||||||
|
self.sections = items.groupedByDate()
|
||||||
|
self.tv.reloadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
func insertFirst() {
|
func insertFirst() {
|
||||||
guard let item = data.first?.clone() else {
|
guard let item = data.first?.shallowClone() else {
|
||||||
reload(animated: false)
|
reload(animated: false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -157,10 +162,23 @@ class RealmSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setFilterPredicate(_ predicate: FilterPredicate<Item>?) {
|
func setFilterPredicate(_ predicate: FilterPredicate<Item>?) {
|
||||||
|
guard self.filterPredicate != nil || predicate != nil else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
self.filterPredicate = predicate
|
self.filterPredicate = predicate
|
||||||
self.reload()
|
self.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setSearchPredicate(_ predicate: FilterPredicate<Item>?) {
|
||||||
|
guard self.searchPredicate != nil || predicate != nil else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.searchPredicate = predicate
|
||||||
|
self.reload()
|
||||||
|
}
|
||||||
|
|
||||||
func makeCsv() throws -> String {
|
func makeCsv() throws -> String {
|
||||||
guard let type = Item.self as? Exportable.Type else {
|
guard let type = Item.self as? Exportable.Type else {
|
||||||
throw CocoaError.error("Type \(Item.self) is not exportable")
|
throw CocoaError.error("Type \(Item.self) is not exportable")
|
||||||
@ -185,9 +203,4 @@ class RealmSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func setQuery(_ query: Results<Item>) {
|
|
||||||
self.data = query
|
|
||||||
startObservingChanges()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import DifferenceKit
|
|
||||||
import RxSwift
|
import RxSwift
|
||||||
import RxCocoa
|
import RxCocoa
|
||||||
import AutoCatCore
|
import AutoCatCore
|
||||||
|
|
||||||
class RxSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource where Item: Dated & Hashable & Differentiable & Decodable, Cell: UITableViewCell & ConfigurableCell, Cell.Item == Item {
|
class RxSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource where Item: Dated & Decodable & Identifiable, Cell: UITableViewCell & ConfigurableCell, Cell.Item == Item {
|
||||||
private var tv: UITableView
|
private var tv: UITableView
|
||||||
private var cellIdentifier: String
|
private var cellIdentifier: String
|
||||||
private var sections: [DateSection<Item>] = []
|
private var sections: [DateSection<Item>] = []
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import RealmSwift
|
import RealmSwift
|
||||||
import DifferenceKit
|
|
||||||
|
|
||||||
public class AudioRecord: Object, Identifiable, Differentiable, Cloneable {
|
public class AudioRecord: Object, Identifiable, Cloneable {
|
||||||
|
|
||||||
@objc public dynamic var path: String = ""
|
@objc public dynamic var path: String = ""
|
||||||
@objc public dynamic var number: String?
|
@objc public dynamic var number: String?
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftDate
|
import SwiftDate
|
||||||
import DifferenceKit
|
|
||||||
|
|
||||||
public struct DateSection<T>: Differentiable, DifferentiableSection, Hashable where T: Differentiable & Hashable {
|
public struct DateSection<T: Identifiable> {
|
||||||
|
|
||||||
var timestamp: Double = 0
|
var timestamp: Double = 0
|
||||||
var dateString: String
|
var dateString: String
|
||||||
@ -12,10 +11,10 @@ public struct DateSection<T>: Differentiable, DifferentiableSection, Hashable wh
|
|||||||
"\(dateString) (\(elements.count))"
|
"\(dateString) (\(elements.count))"
|
||||||
}
|
}
|
||||||
|
|
||||||
public init<C>(source: DateSection<T>, elements: C) where C : Swift.Collection, C.Element == Self.Collection.Element {
|
public init(source: DateSection<T>, elements: [T]) {
|
||||||
self.timestamp = source.timestamp
|
self.timestamp = source.timestamp
|
||||||
self.dateString = source.dateString
|
self.dateString = source.dateString
|
||||||
self.elements = Array(elements)
|
self.elements = elements
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(original: DateSection, items: [T]) {
|
public init(original: DateSection, items: [T]) {
|
||||||
@ -28,9 +27,8 @@ public struct DateSection<T>: Differentiable, DifferentiableSection, Hashable wh
|
|||||||
formatter.dateStyle = .medium
|
formatter.dateStyle = .medium
|
||||||
formatter.timeStyle = .medium
|
formatter.timeStyle = .medium
|
||||||
|
|
||||||
let now = DateInRegion(Date(), region: Region.current)
|
let monthStart = DateCache.shared.monthStart
|
||||||
let monthStart = now.dateAtStartOf(.month)
|
let weekStart = DateCache.shared.weekStart
|
||||||
let weekStart = now.dateAtStartOf(.weekOfMonth)
|
|
||||||
|
|
||||||
let date = Date(timeIntervalSince1970: timestamp)
|
let date = Date(timeIntervalSince1970: timestamp)
|
||||||
let dateInRegion = DateInRegion(date, region: Region.current)
|
let dateInRegion = DateInRegion(date, region: Region.current)
|
||||||
@ -64,7 +62,7 @@ public struct DateSection<T>: Differentiable, DifferentiableSection, Hashable wh
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func index(of element: T) -> Int? {
|
public func index(of element: T) -> Int? {
|
||||||
elements.firstIndex { $0.differenceIdentifier == element.differenceIdentifier }
|
elements.firstIndex { $0.id == element.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
public mutating func replace(_ element: T, at index: Int) {
|
public mutating func replace(_ element: T, at index: Int) {
|
||||||
@ -74,25 +72,4 @@ public struct DateSection<T>: Differentiable, DifferentiableSection, Hashable wh
|
|||||||
public mutating func remove(at index: Int) {
|
public mutating func remove(at index: Int) {
|
||||||
elements.remove(at: index)
|
elements.remove(at: index)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Differentiable
|
|
||||||
|
|
||||||
public var differenceIdentifier: String {
|
|
||||||
return dateString
|
|
||||||
}
|
|
||||||
|
|
||||||
public func isContentEqual(to source: DateSection<T>) -> Bool {
|
|
||||||
return self.differenceIdentifier == source.differenceIdentifier
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Hashable
|
|
||||||
|
|
||||||
public static func == (lhs: DateSection<T>, rhs: DateSection<T>) -> Bool {
|
|
||||||
return lhs.timestamp == rhs.timestamp && lhs.elements == rhs.elements
|
|
||||||
}
|
|
||||||
|
|
||||||
public func hash(into hasher: inout Hasher) {
|
|
||||||
hasher.combine(self.timestamp)
|
|
||||||
hasher.combine(self.elements)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import RealmSwift
|
import RealmSwift
|
||||||
import DifferenceKit
|
|
||||||
|
|
||||||
public class VehicleName: Object, Decodable, Cloneable {
|
public class VehicleName: Object, Decodable, Cloneable {
|
||||||
@objc public dynamic var original: String?
|
@objc public dynamic var original: String?
|
||||||
@ -160,7 +159,7 @@ public class VehicleOwnershipPeriod: Object, Decodable, Cloneable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class Vehicle: Object, Decodable, Identifiable, Differentiable, Cloneable, Exportable {
|
public final class Vehicle: Object, Decodable, Identifiable, Cloneable, Exportable {
|
||||||
@objc public dynamic var brand: VehicleBrand?
|
@objc public dynamic var brand: VehicleBrand?
|
||||||
@objc public dynamic var model: VehicleModel?
|
@objc public dynamic var model: VehicleModel?
|
||||||
@objc public dynamic var color: String?
|
@objc public dynamic var color: String?
|
||||||
@ -194,14 +193,6 @@ public final class Vehicle: Object, Decodable, Identifiable, Differentiable, Clo
|
|||||||
return f
|
return f
|
||||||
}()
|
}()
|
||||||
|
|
||||||
public var differenceIdentifier: String { self.number }
|
|
||||||
|
|
||||||
public func isContentEqual(to source: Vehicle) -> Bool {
|
|
||||||
return self.number == source.number &&
|
|
||||||
self.addedDate == source.addedDate &&
|
|
||||||
self.updatedDate == source.updatedDate
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case brand
|
case brand
|
||||||
case model
|
case model
|
||||||
|
|||||||
36
AutoCatCore/Utils/DateCache.swift
Normal file
36
AutoCatCore/Utils/DateCache.swift
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// DateCache.swift
|
||||||
|
// AutoCatCore
|
||||||
|
//
|
||||||
|
// Created by Selim Mustafaev on 12.02.2023.
|
||||||
|
// Copyright © 2023 Selim Mustafaev. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftDate
|
||||||
|
|
||||||
|
public class DateCache {
|
||||||
|
|
||||||
|
public static let shared = DateCache()
|
||||||
|
|
||||||
|
public var monthStart: DateInRegion
|
||||||
|
public var weekStart: DateInRegion
|
||||||
|
|
||||||
|
public init() {
|
||||||
|
|
||||||
|
let now = DateInRegion(Date(), region: Region.current)
|
||||||
|
self.monthStart = now.dateAtStartOf(.month)
|
||||||
|
self.weekStart = now.dateAtStartOf(.weekOfMonth)
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(self,
|
||||||
|
selector: #selector(dayChanged),
|
||||||
|
name: .NSCalendarDayChanged,
|
||||||
|
object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func dayChanged(_ notification: Notification) {
|
||||||
|
let now = DateInRegion(Date(), region: Region.current)
|
||||||
|
self.monthStart = now.dateAtStartOf(.month)
|
||||||
|
self.weekStart = now.dateAtStartOf(.weekOfMonth)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user