Refactors/macos example

Summary:
Issue fixed: #381

- [X] All tests pass. Demo project builds and runs.
- [ ] I added tests, an experiment, or detailed why my change isn't tested.
- [X] I added an entry to the `CHANGELOG.md` for any breaking changes, enhancements, or bug fixes.
- [X] I have reviewed the [contributing guide](https://github.com/Instagram/IGListKit/blob/master/.github/CONTRIBUTING.md)
Closes https://github.com/Instagram/IGListKit/pull/915

Differential Revision: D5758006

Pulled By: rnystrom

fbshipit-source-id: cbc3f19b6bb9604d8a6c8c15a16414e558e3b70c
This commit is contained in:
Weyert de Boer 2017-09-01 13:49:12 -07:00 committed by Facebook Github Bot
parent 65a5e6de05
commit bb983cf918
5 changed files with 217 additions and 119 deletions

View file

@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
7BF95C4D1F52732200F14EFE /* UserCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7BF95C4C1F52732200F14EFE /* UserCollectionViewCell.xib */; };
7BF95C4F1F5273A100F14EFE /* UserCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BF95C4E1F5273A100F14EFE /* UserCollectionViewCell.swift */; };
888609091DEF38A00019A4A5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 888609081DEF38A00019A4A5 /* AppDelegate.swift */; };
8886090B1DEF38A00019A4A5 /* UsersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8886090A1DEF38A00019A4A5 /* UsersViewController.swift */; };
8886090D1DEF38A00019A4A5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8886090C1DEF38A00019A4A5 /* Assets.xcassets */; };
@ -22,6 +24,8 @@
/* Begin PBXFileReference section */
1CAC2903BAE9D41694D58A7B /* Pods-IGListKitExamples.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IGListKitExamples.release.xcconfig"; path = "Pods/Target Support Files/Pods-IGListKitExamples/Pods-IGListKitExamples.release.xcconfig"; sourceTree = "<group>"; };
63F1F74ED983018C5D607DDC /* Pods_IGListKitExamples.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_IGListKitExamples.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7BF95C4C1F52732200F14EFE /* UserCollectionViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = UserCollectionViewCell.xib; sourceTree = "<group>"; };
7BF95C4E1F5273A100F14EFE /* UserCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserCollectionViewCell.swift; sourceTree = "<group>"; };
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 = "<group>"; };
8886090A1DEF38A00019A4A5 /* UsersViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsersViewController.swift; sourceTree = "<group>"; };
@ -48,6 +52,15 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
7BF95C4B1F5272FA00F14EFE /* View */ = {
isa = PBXGroup;
children = (
7BF95C4C1F52732200F14EFE /* UserCollectionViewCell.xib */,
7BF95C4E1F5273A100F14EFE /* UserCollectionViewCell.swift */,
);
path = View;
sourceTree = "<group>";
};
888608FC1DEF38A00019A4A5 = {
isa = PBXGroup;
children = (
@ -73,6 +86,7 @@
DDE3D8551E0311AF00F96BE4 /* Helpers */,
DDE3D84D1E030A8000F96BE4 /* Models */,
DDE3D84F1E030A9200F96BE4 /* Resources */,
7BF95C4B1F5272FA00F14EFE /* View */,
DDE3D84E1E030A8400F96BE4 /* ViewControllers */,
);
path = IGListKitExamples;
@ -204,6 +218,7 @@
buildActionMask = 2147483647;
files = (
DDE3D8541E03117600F96BE4 /* users.json in Resources */,
7BF95C4D1F52732200F14EFE /* UserCollectionViewCell.xib in Resources */,
8886090D1DEF38A00019A4A5 /* Assets.xcassets in Resources */,
888609101DEF38A00019A4A5 /* Main.storyboard in Resources */,
);
@ -289,6 +304,7 @@
DD9018681E0319E40003789D /* IndexSet+Extensions.swift in Sources */,
888609091DEF38A00019A4A5 /* AppDelegate.swift in Sources */,
DDE3D8511E030AFA00F96BE4 /* User.swift in Sources */,
7BF95C4F1F5273A100F14EFE /* UserCollectionViewCell.swift in Sources */,
DDE3D8571E0311D000F96BE4 /* UsersProvider.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -407,6 +423,7 @@
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = IGListKitExamples/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.11;
PRODUCT_BUNDLE_IDENTIFIER = com.instagram.IGListKitExamples;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
@ -421,6 +438,7 @@
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = IGListKitExamples/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.11;
PRODUCT_BUNDLE_IDENTIFIER = com.instagram.IGListKitExamples;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;

View file

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="12120" systemVersion="16E195" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12120"/>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@ -717,7 +718,7 @@ CA
</toolbar>
</window>
<connections>
<segue destination="XfG-lQ-9wD" kind="relationship" relationship="window.shadowedContentViewController" id="cq2-FE-JQM"/>
<segue destination="YHW-Gd-SIr" kind="relationship" relationship="window.shadowedContentViewController" id="JN0-GW-AMI"/>
</connections>
</windowController>
<customObject id="Oky-zY-oP4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
@ -725,96 +726,58 @@ CA
<point key="canvasLocation" x="75" y="250"/>
</scene>
<!--Users View Controller-->
<scene sceneID="hIz-AP-VOD">
<scene sceneID="Tg7-tm-pfb">
<objects>
<viewController id="XfG-lQ-9wD" customClass="UsersViewController" customModule="IGListKitExamples" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" wantsLayer="YES" id="m2S-Jp-Qdl">
<rect key="frame" x="0.0" y="0.0" width="400" height="500"/>
<viewController id="YHW-Gd-SIr" customClass="UsersViewController" customModule="IGListKitExamples" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" wantsLayer="YES" id="gY5-sL-9mW">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="50" horizontalPageScroll="10" verticalLineScroll="50" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="UBz-hI-v7L">
<rect key="frame" x="0.0" y="0.0" width="400" height="500"/>
<clipView key="contentView" id="CKR-qX-FYp">
<rect key="frame" x="0.0" y="0.0" width="400" height="500"/>
<scrollView autohidesScrollers="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="l6p-IL-yB1">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<clipView key="contentView" drawsBackground="NO" id="Oeu-Bh-ao3">
<rect key="frame" x="1" y="1" width="478" height="268"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" alternatingRowBackgroundColors="YES" columnReordering="NO" columnSelection="YES" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="48" viewBased="YES" id="cZM-LD-xWe">
<rect key="frame" x="0.0" y="0.0" width="400" height="500"/>
<autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="8" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<tableViewGridLines key="gridStyleMask" horizontal="YES"/>
<color key="gridColor" red="0.97900184037837579" green="0.97900184037837579" blue="0.97900184037837579" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<tableColumns>
<tableColumn width="392" minWidth="40" maxWidth="1000" id="4hL-ap-md6">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="zPV-DN-rja">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="cell" id="RNW-L9-uKs">
<rect key="frame" x="4" y="1" width="392" height="48"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField identifier="cell" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="G7m-E1-erw">
<rect key="frame" x="0.0" y="16" width="392" height="17"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="5li-Kx-w3N">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="G7m-E1-erw" firstAttribute="leading" secondItem="RNW-L9-uKs" secondAttribute="leading" constant="2" id="2Ty-cM-jDM"/>
<constraint firstItem="G7m-E1-erw" firstAttribute="centerY" secondItem="RNW-L9-uKs" secondAttribute="centerY" id="OvZ-D7-X77"/>
<constraint firstItem="G7m-E1-erw" firstAttribute="centerX" secondItem="RNW-L9-uKs" secondAttribute="centerX" id="dbG-bh-8jt"/>
</constraints>
<connections>
<outlet property="textField" destination="G7m-E1-erw" id="wk2-cP-5N8"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
</tableColumns>
<collectionView selectable="YES" id="hJf-qq-AxL">
<rect key="frame" x="0.0" y="0.0" width="478" height="268"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" id="gXy-w0-ISW">
<size key="itemSize" width="50" height="50"/>
</collectionViewFlowLayout>
<color key="primaryBackgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="secondaryBackgroundColor" name="controlAlternatingRowColor" catalog="System" colorSpace="catalog"/>
<connections>
<outlet property="dataSource" destination="XfG-lQ-9wD" id="EAO-x6-iol"/>
<outlet property="delegate" destination="XfG-lQ-9wD" id="vDv-jQ-1x9"/>
<outlet property="dataSource" destination="YHW-Gd-SIr" id="gbt-g6-ivY"/>
<outlet property="delegate" destination="YHW-Gd-SIr" id="jWy-xa-UZS"/>
</connections>
</tableView>
</collectionView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="Etj-vY-BHi">
<rect key="frame" x="0.0" y="254" width="400" height="16"/>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="Xsj-La-QKq">
<rect key="frame" x="1" y="144" width="233" height="15"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="WKs-xk-GPd">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="91K-tT-lEx">
<rect key="frame" x="234" y="1" width="15" height="143"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="UBz-hI-v7L" secondAttribute="trailing" id="SuS-t1-8hF"/>
<constraint firstItem="UBz-hI-v7L" firstAttribute="top" secondItem="m2S-Jp-Qdl" secondAttribute="top" id="Suz-Mg-sXx"/>
<constraint firstAttribute="bottom" secondItem="UBz-hI-v7L" secondAttribute="bottom" id="dBT-so-LaR"/>
<constraint firstItem="UBz-hI-v7L" firstAttribute="leading" secondItem="m2S-Jp-Qdl" secondAttribute="leading" id="lt2-xn-gAQ"/>
<constraint firstItem="l6p-IL-yB1" firstAttribute="top" secondItem="gY5-sL-9mW" secondAttribute="top" id="81q-nn-rn1"/>
<constraint firstAttribute="trailing" secondItem="l6p-IL-yB1" secondAttribute="trailing" id="MKh-Ym-9LE"/>
<constraint firstItem="l6p-IL-yB1" firstAttribute="leading" secondItem="gY5-sL-9mW" secondAttribute="leading" id="ZHB-t7-kZq"/>
<constraint firstAttribute="bottom" secondItem="l6p-IL-yB1" secondAttribute="bottom" id="oar-xD-J5C"/>
</constraints>
</view>
<connections>
<outlet property="tableView" destination="cZM-LD-xWe" id="fsK-gP-bU1"/>
<outlet property="collectionView" destination="hJf-qq-AxL" id="ZdG-57-x5d"/>
</connections>
</viewController>
<customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
<customObject id="zd7-Dq-nvB" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="923"/>
<point key="canvasLocation" x="75" y="806"/>
</scene>
</scenes>
</document>

View file

@ -0,0 +1,29 @@
//
// UserCollectionViewCell.swift
// IGListKitExamples
//
// Created by Weyert de Boer on 27/08/2017.
// Copyright © 2017 Instagram. All rights reserved.
//
import Cocoa
protocol UserCollectionViewCellDelegate: class {
func itemDeleted(_ user: User)
}
final class UserCollectionViewCell: NSCollectionViewItem {
weak var delegate: UserCollectionViewCellDelegate?
@IBAction func deleteButtonClicked(_ sender: AnyObject) {
guard let user = representedObject as? User else { return }
delegate?.itemDeleted(user)
}
func bindViewModel(_ user: User) {
representedObject = user
textField?.stringValue = user.name
}
}

View file

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner"/>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<collectionViewItem id="xP0-v7-EFE" customClass="UserCollectionViewCell" customModule="IGListKitExamples" customModuleProvider="target">
<connections>
<outlet property="textField" destination="Ypj-SS-m9s" id="BMD-zR-IB8"/>
<outlet property="view" destination="NIs-9L-IO1" id="eUJ-SQ-WOE"/>
</connections>
</collectionViewItem>
<customView id="NIs-9L-IO1">
<rect key="frame" x="0.0" y="0.0" width="480" height="47"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="zDT-HW-k24" userLabel="Content View">
<rect key="frame" x="5" y="5" width="470" height="37"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ypj-SS-m9s">
<rect key="frame" x="8" y="10" width="454" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="User Name" id="EMD-h2-FhR">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="OMr-G1-CjN">
<rect key="frame" x="381" y="1" width="80" height="32"/>
<buttonCell key="cell" type="push" title="Delete" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="FAm-Ie-kke">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="deleteButtonClicked:" target="xP0-v7-EFE" id="qoH-AN-ZwB"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="Ypj-SS-m9s" firstAttribute="leading" secondItem="zDT-HW-k24" secondAttribute="leading" constant="10" id="Ihp-O8-Efo"/>
<constraint firstAttribute="trailing" secondItem="OMr-G1-CjN" secondAttribute="trailing" constant="15" id="KAP-rQ-1IY"/>
<constraint firstItem="OMr-G1-CjN" firstAttribute="centerY" secondItem="zDT-HW-k24" secondAttribute="centerY" id="OpF-yx-eVP"/>
<constraint firstItem="Ypj-SS-m9s" firstAttribute="top" secondItem="zDT-HW-k24" secondAttribute="top" constant="10" id="fgx-2W-GAC"/>
<constraint firstAttribute="bottom" secondItem="Ypj-SS-m9s" secondAttribute="bottom" constant="10" id="l0W-g6-GHk"/>
<constraint firstAttribute="trailing" secondItem="Ypj-SS-m9s" secondAttribute="trailing" constant="10" id="ngw-Cc-XUB"/>
</constraints>
</customView>
</subviews>
<constraints>
<constraint firstItem="zDT-HW-k24" firstAttribute="leading" secondItem="NIs-9L-IO1" secondAttribute="leading" constant="5" id="Osd-B8-bXz"/>
<constraint firstItem="zDT-HW-k24" firstAttribute="top" secondItem="NIs-9L-IO1" secondAttribute="top" constant="5" id="UWq-nv-EsM"/>
<constraint firstAttribute="bottom" secondItem="zDT-HW-k24" secondAttribute="bottom" constant="5" id="rt6-4r-5RK"/>
<constraint firstAttribute="trailing" secondItem="zDT-HW-k24" secondAttribute="trailing" constant="5" id="tQO-4a-4WN"/>
</constraints>
</customView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="NO" id="MA2-Td-T4f">
<rect key="frame" x="0.0" y="0.0" width="38" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Label" id="Mhd-Im-KQa">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</objects>
</document>

View file

@ -17,7 +17,7 @@ import IGListKit
final class UsersViewController: NSViewController {
@IBOutlet weak var tableView: NSTableView!
@IBOutlet weak var collectionView: NSCollectionView!
// MARK: Data
@ -51,22 +51,40 @@ final class UsersViewController: NSViewController {
// MARK: -
// MARK: Diffing
var isFirstRun = true
var filteredUsers = [User]() {
didSet {
// get the difference between the old array of Users and the new array of Users
let diff = ListDiff(oldArray: oldValue, newArray: filteredUsers, option: .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)
// A crash occurs when you try to use performBatchUpdates the first time
guard !isFirstRun else {
collectionView.reloadData()
isFirstRun = false
return
}
tableView.endUpdates()
// get the difference between the old array of Users and the new array of Users
let diff = ListDiffPaths(fromSection: 0, toSection: 0, oldArray: oldValue, newArray: filteredUsers, option: .equality)
let batchUpdates = diff.forBatchUpdates()
let inserts = Set(batchUpdates.inserts)
let deletes = Set(batchUpdates.deletes)
let updates = Set(batchUpdates.updates)
let moves = Set(batchUpdates.moves)
// this difference is used here to update the collection 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 NSCollectionView ;)
// Set the animation duration when updating the collection view
NSAnimationContext.current().duration = 0.25
// Perform the updates to the collection view
collectionView.animator().performBatchUpdates({
collectionView.deleteItems(at: deletes)
collectionView.insertItems(at: inserts)
collectionView.reloadItems(at: updates)
moves.forEach { move in
collectionView.moveItem(at: move.from, to: move.to)
}
}, completionHandler: nil)
}
}
@ -87,6 +105,9 @@ final class UsersViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// The view needs to be backed by a CALayer to be able to enable the collections view animations you can
// enable this by selecting the view controller's view in the Interface Builder in the Core Animation section
// of the View Effects inspector tab, through code you can do by view.wantsLayer = true
loadSampleUsers()
}
@ -103,48 +124,44 @@ final class UsersViewController: NSViewController {
@IBAction func search(_ sender: NSSearchField) {
searchTerm = sender.stringValue
}
@IBAction func delete(_ sender: Any?) {
guard !tableView.selectedRowIndexes.isEmpty 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: UserCollectionViewCellDelegate {
func itemDeleted(_ user: User) {
self.delete(user: user)
}
}
extension UsersViewController: NSTableViewDelegate {
extension UsersViewController: NSCollectionViewDelegate {
}
extension UsersViewController: NSCollectionViewDataSource {
private struct Storyboard {
static let cellIdentifier = "cell"
static let cellIdentifier = "UserCollectionViewCell"
}
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
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return self.filteredUsers.count
}
@available(OSX 10.11, *)
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
let item = collectionView.makeItem(withIdentifier: Storyboard.cellIdentifier, for: indexPath)
guard let cell = item as? UserCollectionViewCell else { return item }
cell.delegate = self
cell.bindViewModel(filteredUsers[indexPath.item])
return cell
}
@available(OSX 10.11, *)
func tableView(_ tableView: NSTableView, rowActionsForRow row: Int, edge: NSTableRowActionEdge) -> [NSTableViewRowAction] {
let delete = NSTableViewRowAction(style: .destructive, title: "Delete") { _, row in
guard row < self.filteredUsers.count else { return }
self.delete(user: self.filteredUsers[row])
}
return [delete]
}
}
extension UsersViewController: NSCollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize {
let availableWidth = collectionView.bounds.width
return CGSize(width: availableWidth, height: 44)
}
}