diff --git a/Examples/Examples-macOS/IGListKitExamples.xcodeproj/project.pbxproj b/Examples/Examples-macOS/IGListKitExamples.xcodeproj/project.pbxproj index 40f3488a..bedb02ae 100644 --- a/Examples/Examples-macOS/IGListKitExamples.xcodeproj/project.pbxproj +++ b/Examples/Examples-macOS/IGListKitExamples.xcodeproj/project.pbxproj @@ -8,9 +8,14 @@ /* Begin PBXBuildFile section */ 888609091DEF38A00019A4A5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 888609081DEF38A00019A4A5 /* AppDelegate.swift */; }; - 8886090B1DEF38A00019A4A5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8886090A1DEF38A00019A4A5 /* ViewController.swift */; }; + 8886090B1DEF38A00019A4A5 /* UsersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8886090A1DEF38A00019A4A5 /* UsersViewController.swift */; }; 8886090D1DEF38A00019A4A5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8886090C1DEF38A00019A4A5 /* Assets.xcassets */; }; 888609101DEF38A00019A4A5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8886090E1DEF38A00019A4A5 /* Main.storyboard */; }; + DD9018681E0319E40003789D /* IndexSet+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9018671E0319E40003789D /* IndexSet+Extensions.swift */; }; + DD90186A1E031A3E0003789D /* Shuffle.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9018691E031A3E0003789D /* Shuffle.swift */; }; + DDE3D8511E030AFA00F96BE4 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE3D8501E030AFA00F96BE4 /* User.swift */; }; + DDE3D8541E03117600F96BE4 /* users.json in Resources */ = {isa = PBXBuildFile; fileRef = DDE3D8531E03117600F96BE4 /* users.json */; }; + DDE3D8571E0311D000F96BE4 /* UsersProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE3D8561E0311D000F96BE4 /* UsersProvider.swift */; }; E451550281456814F7F659DB /* Pods_IGListKitExamples.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 63F1F74ED983018C5D607DDC /* Pods_IGListKitExamples.framework */; }; /* End PBXBuildFile section */ @@ -19,11 +24,16 @@ 63F1F74ED983018C5D607DDC /* Pods_IGListKitExamples.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_IGListKitExamples.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 888609051DEF38A00019A4A5 /* IGListKitExamples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IGListKitExamples.app; sourceTree = BUILT_PRODUCTS_DIR; }; 888609081DEF38A00019A4A5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 8886090A1DEF38A00019A4A5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 8886090A1DEF38A00019A4A5 /* UsersViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsersViewController.swift; sourceTree = ""; }; 8886090C1DEF38A00019A4A5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 8886090F1DEF38A00019A4A5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 888609111DEF38A00019A4A5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C80289D4560E0726CF3F86E0 /* Pods-IGListKitExamples.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IGListKitExamples.debug.xcconfig"; path = "Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.debug.xcconfig"; sourceTree = ""; }; + DD9018671E0319E40003789D /* IndexSet+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "IndexSet+Extensions.swift"; sourceTree = ""; }; + DD9018691E031A3E0003789D /* Shuffle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Shuffle.swift; sourceTree = ""; }; + DDE3D8501E030AFA00F96BE4 /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; + DDE3D8531E03117600F96BE4 /* users.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = users.json; sourceTree = ""; }; + DDE3D8561E0311D000F96BE4 /* UsersProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsersProvider.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -60,10 +70,10 @@ isa = PBXGroup; children = ( 888609081DEF38A00019A4A5 /* AppDelegate.swift */, - 8886090A1DEF38A00019A4A5 /* ViewController.swift */, - 8886090C1DEF38A00019A4A5 /* Assets.xcassets */, - 8886090E1DEF38A00019A4A5 /* Main.storyboard */, - 888609111DEF38A00019A4A5 /* Info.plist */, + DDE3D8551E0311AF00F96BE4 /* Helpers */, + DDE3D84F1E030A9200F96BE4 /* Resources */, + DDE3D84E1E030A8400F96BE4 /* ViewControllers */, + DDE3D84D1E030A8000F96BE4 /* Models */, ); path = IGListKitExamples; sourceTree = ""; @@ -85,6 +95,51 @@ name = Frameworks; sourceTree = ""; }; + DDE3D84D1E030A8000F96BE4 /* Models */ = { + isa = PBXGroup; + children = ( + DDE3D8501E030AFA00F96BE4 /* User.swift */, + ); + path = Models; + sourceTree = ""; + }; + DDE3D84E1E030A8400F96BE4 /* ViewControllers */ = { + isa = PBXGroup; + children = ( + 8886090A1DEF38A00019A4A5 /* UsersViewController.swift */, + ); + path = ViewControllers; + sourceTree = ""; + }; + DDE3D84F1E030A9200F96BE4 /* Resources */ = { + isa = PBXGroup; + children = ( + DDE3D8521E03117600F96BE4 /* Data */, + 8886090C1DEF38A00019A4A5 /* Assets.xcassets */, + 8886090E1DEF38A00019A4A5 /* Main.storyboard */, + 888609111DEF38A00019A4A5 /* Info.plist */, + ); + name = Resources; + sourceTree = ""; + }; + DDE3D8521E03117600F96BE4 /* Data */ = { + isa = PBXGroup; + children = ( + DDE3D8531E03117600F96BE4 /* users.json */, + ); + path = Data; + sourceTree = ""; + }; + DDE3D8551E0311AF00F96BE4 /* Helpers */ = { + isa = PBXGroup; + children = ( + DDE3D8561E0311D000F96BE4 /* UsersProvider.swift */, + DD9018671E0319E40003789D /* IndexSet+Extensions.swift */, + DD9018691E031A3E0003789D /* Shuffle.swift */, + ); + path = Helpers; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -147,6 +202,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + DDE3D8541E03117600F96BE4 /* users.json in Resources */, 8886090D1DEF38A00019A4A5 /* Assets.xcassets in Resources */, 888609101DEF38A00019A4A5 /* Main.storyboard in Resources */, ); @@ -207,8 +263,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 8886090B1DEF38A00019A4A5 /* ViewController.swift in Sources */, + 8886090B1DEF38A00019A4A5 /* UsersViewController.swift in Sources */, + DD90186A1E031A3E0003789D /* Shuffle.swift in Sources */, + DD9018681E0319E40003789D /* IndexSet+Extensions.swift in Sources */, 888609091DEF38A00019A4A5 /* AppDelegate.swift in Sources */, + DDE3D8511E030AFA00F96BE4 /* User.swift in Sources */, + DDE3D8571E0311D000F96BE4 /* UsersProvider.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Examples/Examples-macOS/IGListKitExamples/Base.lproj/Main.storyboard b/Examples/Examples-macOS/IGListKitExamples/Base.lproj/Main.storyboard index f0b29903..667be0cc 100644 --- a/Examples/Examples-macOS/IGListKitExamples/Base.lproj/Main.storyboard +++ b/Examples/Examples-macOS/IGListKitExamples/Base.lproj/Main.storyboard @@ -1,9 +1,10 @@ - + - + + @@ -156,6 +157,9 @@ + +CA + @@ -658,17 +662,61 @@ - + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -678,38 +726,97 @@ - + - + - + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + - + diff --git a/Examples/Examples-macOS/IGListKitExamples/Data/users.json b/Examples/Examples-macOS/IGListKitExamples/Data/users.json new file mode 100644 index 00000000..2dec3fb5 --- /dev/null +++ b/Examples/Examples-macOS/IGListKitExamples/Data/users.json @@ -0,0 +1,603 @@ +[{ + "name": "AMELIA" +}, +{ + "name": "OLIVIA" +}, +{ + "name": "EMILY" +}, +{ + "name": "AVA" +}, +{ + "name": "ISLA" +}, +{ + "name": "JESSICA" +}, +{ + "name": "POPPY" +}, +{ + "name": "ISABELLA" +}, +{ + "name": "SOPHIE" +}, +{ + "name": "MIA" +}, +{ + "name": "RUBY" +}, +{ + "name": "LILY" +}, +{ + "name": "GRACE" +}, +{ + "name": "EVIE" +}, +{ + "name": "SOPHIA" +}, +{ + "name": "ELLA" +}, +{ + "name": "SCARLETT" +}, +{ + "name": "CHLOE" +}, +{ + "name": "ISABELLE" +}, +{ + "name": "FREYA" +}, +{ + "name": "CHARLOTTE" +}, +{ + "name": "SIENNA" +}, +{ + "name": "DAISY" +}, +{ + "name": "PHOEBE" +}, +{ + "name": "MILLIE" +}, +{ + "name": "EVA" +}, +{ + "name": "ALICE" +}, +{ + "name": "LUCY" +}, +{ + "name": "FLORENCE" +}, +{ + "name": "SOFIA" +}, +{ + "name": "LAYLA" +}, +{ + "name": "LOLA" +}, +{ + "name": "HOLLY" +}, +{ + "name": "IMOGEN" +}, +{ + "name": "MOLLY" +}, +{ + "name": "MATILDA" +}, +{ + "name": "LILLY" +}, +{ + "name": "ROSIE" +}, +{ + "name": "ELIZABETH" +}, +{ + "name": "ERIN" +}, +{ + "name": "MAISIE" +}, +{ + "name": "LEXI" +}, +{ + "name": "ELLIE" +}, +{ + "name": "HANNAH" +}, +{ + "name": "EVELYN" +}, +{ + "name": "ABIGAIL" +}, +{ + "name": "ELSIE" +}, +{ + "name": "SUMMER" +}, +{ + "name": "MEGAN" +}, +{ + "name": "JASMINE" +}, +{ + "name": "MAYA" +}, +{ + "name": "AMELIE" +}, +{ + "name": "LACEY" +}, +{ + "name": "WILLOW" +}, +{ + "name": "EMMA" +}, +{ + "name": "BELLA" +}, +{ + "name": "ELEANOR" +}, +{ + "name": "ESME" +}, +{ + "name": "ELIZA" +}, +{ + "name": "GEORGIA" +}, +{ + "name": "HARRIET" +}, +{ + "name": "GRACIE" +}, +{ + "name": "ANNABELLE" +}, +{ + "name": "EMILIA" +}, +{ + "name": "AMBER" +}, +{ + "name": "IVY" +}, +{ + "name": "BROOKE" +}, +{ + "name": "ROSE" +}, +{ + "name": "ANNA" +}, +{ + "name": "ZARA" +}, +{ + "name": "LEAH" +}, +{ + "name": "MOLLIE" +}, +{ + "name": "MARTHA" +}, +{ + "name": "FAITH" +}, +{ + "name": "HOLLIE" +}, +{ + "name": "AMY" +}, +{ + "name": "BETHANY" +}, +{ + "name": "VIOLET" +}, +{ + "name": "KATIE" +}, +{ + "name": "MARYAM" +}, +{ + "name": "FRANCESCA" +}, +{ + "name": "JULIA" +}, +{ + "name": "MARIA" +}, +{ + "name": "DARCEY" +}, +{ + "name": "ISABEL" +}, +{ + "name": "TILLY" +}, +{ + "name": "MADDISON" +}, +{ + "name": "VICTORIA" +}, +{ + "name": "ISOBEL" +}, +{ + "name": "NIAMH" +}, +{ + "name": "SKYE" +}, +{ + "name": "MADISON" +}, +{ + "name": "DARCY" +}, +{ + "name": "AISHA" +}, +{ + "name": "BEATRICE" +}, +{ + "name": "SARAH" +}, +{ + "name": "ZOE" +}, +{ + "name": "PAIGE" +}, +{ + "name": "HEIDI" +}, +{ + "name": "LYDIA" +}, +{ + "name": "SARA" +}, +{ + "name": "OLIVER" +}, +{ + "name": "JACK" +}, +{ + "name": "HARRY" +}, +{ + "name": "JACOB" +}, +{ + "name": "CHARLIE" +}, +{ + "name": "THOMAS" +}, +{ + "name": "OSCAR" +}, +{ + "name": "WILLIAM" +}, +{ + "name": "JAMES" +}, +{ + "name": "GEORGE" +}, +{ + "name": "ALFIE" +}, +{ + "name": "JOSHUA" +}, +{ + "name": "NOAH" +}, +{ + "name": "ETHAN" +}, +{ + "name": "MUHAMMAD" +}, +{ + "name": "ARCHIE" +}, +{ + "name": "LEO" +}, +{ + "name": "HENRY" +}, +{ + "name": "JOSEPH" +}, +{ + "name": "SAMUEL" +}, +{ + "name": "RILEY" +}, +{ + "name": "DANIEL" +}, +{ + "name": "MOHAMMED" +}, +{ + "name": "ALEXANDER" +}, +{ + "name": "MAX" +}, +{ + "name": "LUCAS" +}, +{ + "name": "MASON" +}, +{ + "name": "LOGAN" +}, +{ + "name": "ISAAC" +}, +{ + "name": "BENJAMIN" +}, +{ + "name": "DYLAN" +}, +{ + "name": "JAKE" +}, +{ + "name": "EDWARD" +}, +{ + "name": "FINLEY" +}, +{ + "name": "FREDDIE" +}, +{ + "name": "HARRISON" +}, +{ + "name": "TYLER" +}, +{ + "name": "SEBASTIAN" +}, +{ + "name": "ZACHARY" +}, +{ + "name": "ADAM" +}, +{ + "name": "THEO" +}, +{ + "name": "JAYDEN" +}, +{ + "name": "ARTHUR" +}, +{ + "name": "TOBY" +}, +{ + "name": "LUKE" +}, +{ + "name": "LEWIS" +}, +{ + "name": "MATTHEW" +}, +{ + "name": "HARVEY" +}, +{ + "name": "HARLEY" +}, +{ + "name": "DAVID" +}, +{ + "name": "RYAN" +}, +{ + "name": "TOMMY" +}, +{ + "name": "MICHAEL" +}, +{ + "name": "REUBEN" +}, +{ + "name": "NATHAN" +}, +{ + "name": "BLAKE" +}, +{ + "name": "MOHAMMAD" +}, +{ + "name": "JENSON" +}, +{ + "name": "BOBBY" +}, +{ + "name": "LUCA" +}, +{ + "name": "CHARLES" +}, +{ + "name": "FRANKIE" +}, +{ + "name": "DEXTER" +}, +{ + "name": "KAI" +}, +{ + "name": "ALEX" +}, +{ + "name": "CONNOR" +}, +{ + "name": "LIAM" +}, +{ + "name": "JAMIE" +}, +{ + "name": "ELIJAH" +}, +{ + "name": "STANLEY" +}, +{ + "name": "LOUIE" +}, +{ + "name": "JUDE" +}, +{ + "name": "CALLUM" +}, +{ + "name": "HUGO" +}, +{ + "name": "LEON" +}, +{ + "name": "ELLIOT" +}, +{ + "name": "LOUIS" +}, +{ + "name": "THEODORE" +}, +{ + "name": "GABRIEL" +}, +{ + "name": "OLLIE" +}, +{ + "name": "AARON" +}, +{ + "name": "FREDERICK" +}, +{ + "name": "EVAN" +}, +{ + "name": "ELLIOTT" +}, +{ + "name": "OWEN" +}, +{ + "name": "TEDDY" +}, +{ + "name": "FINLAY" +}, +{ + "name": "CALEB" +}, +{ + "name": "IBRAHIM" +}, +{ + "name": "RONNIE" +}, +{ + "name": "FELIX" +}, +{ + "name": "AIDEN" +}, +{ + "name": "CAMERON" +}, +{ + "name": "AUSTIN" +}, +{ + "name": "KIAN" +}, +{ + "name": "RORY" +}, +{ + "name": "SETH" +}, +{ + "name": "ROBERT" +}, +{ + "name": "ALBERT" +}, +{ + "name": "SONNY" +}] \ No newline at end of file diff --git a/Examples/Examples-macOS/IGListKitExamples/ViewController.swift b/Examples/Examples-macOS/IGListKitExamples/Helpers/IndexSet+Extensions.swift similarity index 71% rename from Examples/Examples-macOS/IGListKitExamples/ViewController.swift rename to Examples/Examples-macOS/IGListKitExamples/Helpers/IndexSet+Extensions.swift index ebd500d0..3249698b 100644 --- a/Examples/Examples-macOS/IGListKitExamples/ViewController.swift +++ b/Examples/Examples-macOS/IGListKitExamples/Helpers/IndexSet+Extensions.swift @@ -1,9 +1,9 @@ /** Copyright (c) 2016-present, Facebook, Inc. All rights reserved. - + The examples provided by Facebook are for non-commercial testing and evaluation purposes only. Facebook reserves all rights not expressly granted. - + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL @@ -12,19 +12,12 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import Cocoa -import IGListKit +import Foundation -final class ViewController: NSViewController { - - override func viewDidLoad() { - super.viewDidLoad() - } - - override var representedObject: Any? { - didSet { - // Update the view, if already loaded. - } +extension IndexSet { + + static var zero: IndexSet { + return NSIndexSet(index: 0) as IndexSet } + } - diff --git a/Examples/Examples-macOS/IGListKitExamples/Helpers/Shuffle.swift b/Examples/Examples-macOS/IGListKitExamples/Helpers/Shuffle.swift new file mode 100644 index 00000000..7dbaf95e --- /dev/null +++ b/Examples/Examples-macOS/IGListKitExamples/Helpers/Shuffle.swift @@ -0,0 +1,46 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import Foundation + +extension MutableCollection where Indices.Iterator.Element == Index { + + /// Shuffles the contents of this collection. + mutating func shuffle() { + let c = count + guard c > 1 else { return } + + for (firstUnshuffled , unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) { + let d: IndexDistance = numericCast(arc4random_uniform(numericCast(unshuffledCount))) + guard d != 0 else { continue } + + let i = index(firstUnshuffled, offsetBy: d) + + swap(&self[firstUnshuffled], &self[i]) + } + } + +} + +extension Sequence { + + /// Returns an array with the contents of this sequence, shuffled. + var shuffled: [Iterator.Element] { + var result = Array(self) + result.shuffle() + + return result + } + +} diff --git a/Examples/Examples-macOS/IGListKitExamples/Helpers/UsersProvider.swift b/Examples/Examples-macOS/IGListKitExamples/Helpers/UsersProvider.swift new file mode 100644 index 00000000..69f9094c --- /dev/null +++ b/Examples/Examples-macOS/IGListKitExamples/Helpers/UsersProvider.swift @@ -0,0 +1,40 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import Foundation + +final class UsersProvider { + + enum UsersError: Error { + case invalidData + } + + let users: [User] + + init(with file: URL) throws { + let data = try Data(contentsOf: file) + let json = try JSONSerialization.jsonObject(with: data, options: []) + + guard let dicts = json as? [[String: String]] else { + throw UsersError.invalidData + } + + self.users = dicts.enumerated().flatMap { index, dict in + guard let name = dict["name"] else { return nil } + + return User(pk: index, name: name.capitalized) + }.sorted(by: { $0.name < $1.name }) + } + +} diff --git a/Examples/Examples-macOS/IGListKitExamples/Models/User.swift b/Examples/Examples-macOS/IGListKitExamples/Models/User.swift new file mode 100644 index 00000000..0c52ce64 --- /dev/null +++ b/Examples/Examples-macOS/IGListKitExamples/Models/User.swift @@ -0,0 +1,40 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import Foundation +import IGListKit + +final class User: IGListDiffable { + + let pk: Int + let name: String + + init(pk: Int, name: String) { + self.pk = pk + self.name = name + } + + //MARK: IGListDiffable + + func diffIdentifier() -> NSObjectProtocol { + return pk as NSObjectProtocol + } + + func isEqual(toDiffableObject object: IGListDiffable?) -> Bool { + guard self !== object else { return true } + guard let object = object as? User else { return false } + return name == object.name + } + +} diff --git a/Examples/Examples-macOS/IGListKitExamples/ViewControllers/UsersViewController.swift b/Examples/Examples-macOS/IGListKitExamples/ViewControllers/UsersViewController.swift new file mode 100644 index 00000000..b0c003c6 --- /dev/null +++ b/Examples/Examples-macOS/IGListKitExamples/ViewControllers/UsersViewController.swift @@ -0,0 +1,149 @@ +/** + Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + + The examples provided by Facebook are for non-commercial testing and evaluation + purposes only. Facebook reserves all rights not expressly granted. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import Cocoa +import IGListKit + +final class UsersViewController: NSViewController { + + @IBOutlet weak var tableView: NSTableView! + + //MARK: Data + + var users = [User]() { + didSet { + computeFilteredUsers() + } + } + + var searchTerm = "" { + didSet { + computeFilteredUsers() + } + } + + private func computeFilteredUsers() { + guard searchTerm.characters.count > 1 else { + filteredUsers = users + return + } + + filteredUsers = users.filter({ $0.name.localizedCaseInsensitiveContains(self.searchTerm) }) + } + + fileprivate func delete(user: User) { + guard let index = self.users.index(where: { $0.pk == user.pk }) else { return } + + self.users.remove(at: index) + } + + //MARK: - + //MARK: Diffing + + var filteredUsers = [User]() { + didSet { + // get the difference between the old array of Users and the new array of Users + let diff = IGListDiff(oldValue, filteredUsers, .equality) + + // this difference is used here to update the table view, but it can be used + // to update collection views and other similar interface elements + // this code can also be added to an extension of NSTableView ;) + tableView.beginUpdates() + tableView.insertRows(at: diff.inserts, withAnimation: .slideDown) + tableView.removeRows(at: diff.deletes, withAnimation: .slideUp) + tableView.reloadData(forRowIndexes: diff.updates, columnIndexes: .zero) + diff.moves.forEach { move in + self.tableView.moveRow(at: move.from, to: move.to) + } + tableView.endUpdates() + } + } + + //MARK: - + + private func loadSampleUsers() { + guard let file = Bundle.main.url(forResource: "users", withExtension: "json") else { return } + + do { + self.users = try UsersProvider(with: file).users + } catch { + NSAlert(error: error).runModal() + } + } + + // MARK: Interface + + override func viewDidLoad() { + super.viewDidLoad() + + loadSampleUsers() + } + + override func viewDidAppear() { + super.viewDidAppear() + + view.window?.titleVisibility = .hidden + } + + @IBAction func shuffle(_ sender: Any?) { + users = users.shuffled + } + + @IBAction func search(_ sender: NSSearchField) { + searchTerm = sender.stringValue + } + + @IBAction func delete(_ sender: Any?) { + guard tableView.selectedRowIndexes.count > 0 else { return } + + tableView.selectedRowIndexes.forEach({ self.delete(user: self.filteredUsers[$0]) }) + } + +} + +extension UsersViewController: NSTableViewDataSource { + + func numberOfRows(in tableView: NSTableView) -> Int { + return filteredUsers.count + } + +} + +extension UsersViewController: NSTableViewDelegate { + + private struct Storyboard { + static let cellIdentifier = "cell" + } + + func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + guard let cell = tableView.make(withIdentifier: Storyboard.cellIdentifier, owner: tableView) as? NSTableCellView else { + return nil + } + + cell.textField?.stringValue = filteredUsers[row].name + + return cell + } + + func tableView(_ tableView: NSTableView, rowActionsForRow row: Int, edge: NSTableRowActionEdge) -> [NSTableViewRowAction] { + let delete = NSTableViewRowAction(style: .destructive, title: "Delete") { action, row in + guard row < self.filteredUsers.count else { return } + + self.delete(user: self.filteredUsers[row]) + } + + return [delete] + } + +}