macOS example app

Summary:
Related issue: #333

I've made a very simple macOS example app. It's just a list of names which can be searched, shuffled or deleted.

I think this is a good starting point for anyone who wants to use this on macOS projects :)

![iglistkitmac](https://cloud.githubusercontent.com/assets/67184/21238494/7245f242-c2ea-11e6-98ea-218a6150d14c.gif)
Closes https://github.com/Instagram/IGListKit/pull/337

Reviewed By: rnystrom

Differential Revision: D4345236

Pulled By: jessesquires

fbshipit-source-id: ce75372263d3f451e34f2c816c14ab6bc82116a7
This commit is contained in:
Guilherme Rambo
2016-12-19 09:13:21 -08:00
committed by Facebook Github Bot
parent 1765d68406
commit 47fbb72fa6
8 changed files with 1086 additions and 48 deletions

View File

@@ -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 = "<group>"; };
8886090A1DEF38A00019A4A5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
8886090A1DEF38A00019A4A5 /* UsersViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsersViewController.swift; sourceTree = "<group>"; };
8886090C1DEF38A00019A4A5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
8886090F1DEF38A00019A4A5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
888609111DEF38A00019A4A5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
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 = "<group>"; };
DD9018671E0319E40003789D /* IndexSet+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "IndexSet+Extensions.swift"; sourceTree = "<group>"; };
DD9018691E031A3E0003789D /* Shuffle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Shuffle.swift; sourceTree = "<group>"; };
DDE3D8501E030AFA00F96BE4 /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
DDE3D8531E03117600F96BE4 /* users.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = users.json; sourceTree = "<group>"; };
DDE3D8561E0311D000F96BE4 /* UsersProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UsersProvider.swift; sourceTree = "<group>"; };
/* 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 = "<group>";
@@ -85,6 +95,51 @@
name = Frameworks;
sourceTree = "<group>";
};
DDE3D84D1E030A8000F96BE4 /* Models */ = {
isa = PBXGroup;
children = (
DDE3D8501E030AFA00F96BE4 /* User.swift */,
);
path = Models;
sourceTree = "<group>";
};
DDE3D84E1E030A8400F96BE4 /* ViewControllers */ = {
isa = PBXGroup;
children = (
8886090A1DEF38A00019A4A5 /* UsersViewController.swift */,
);
path = ViewControllers;
sourceTree = "<group>";
};
DDE3D84F1E030A9200F96BE4 /* Resources */ = {
isa = PBXGroup;
children = (
DDE3D8521E03117600F96BE4 /* Data */,
8886090C1DEF38A00019A4A5 /* Assets.xcassets */,
8886090E1DEF38A00019A4A5 /* Main.storyboard */,
888609111DEF38A00019A4A5 /* Info.plist */,
);
name = Resources;
sourceTree = "<group>";
};
DDE3D8521E03117600F96BE4 /* Data */ = {
isa = PBXGroup;
children = (
DDE3D8531E03117600F96BE4 /* users.json */,
);
path = Data;
sourceTree = "<group>";
};
DDE3D8551E0311AF00F96BE4 /* Helpers */ = {
isa = PBXGroup;
children = (
DDE3D8561E0311D000F96BE4 /* UsersProvider.swift */,
DD9018671E0319E40003789D /* IndexSet+Extensions.swift */,
DD9018691E031A3E0003789D /* Shuffle.swift */,
);
path = Helpers;
sourceTree = "<group>";
};
/* 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;
};

View File

@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="11762" systemVersion="16C67" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="11542" systemVersion="16B2657" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11762"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11542"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
<capability name="system font weights other than Regular or Bold" minToolsVersion="7.0"/>
</dependencies>
<scenes>
<!--Application-->
@@ -156,6 +157,9 @@
</connections>
</menuItem>
<menuItem title="Delete" id="pa3-QI-u2k">
<string key="keyEquivalent" base64-UTF8="YES">
CA
</string>
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="Ady-hI-5gd" id="0Mk-Ml-PaM"/>
@@ -658,17 +662,61 @@
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="IGListKitExamples" customModuleProvider="target"/>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="0.0"/>
<point key="canvasLocation" x="69" y="-171"/>
</scene>
<!--Window Controller-->
<scene sceneID="R2V-B0-nI4">
<objects>
<windowController id="B8D-0N-5wS" sceneMemberID="viewController">
<window key="window" title="IGListKit" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="IQv-IB-iLA">
<window key="window" title="IGListKit" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="documentWindow" id="IQv-IB-iLA">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="270"/>
<rect key="contentRect" x="196" y="240" width="400" height="500"/>
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
<value key="minSize" type="size" width="400" height="500"/>
<toolbar key="toolbar" implicitIdentifier="5933590F-D75C-4F85-9CF5-9F8C642BEB7E" autosavesConfiguration="NO" allowsUserCustomization="NO" displayMode="iconOnly" sizeMode="regular" id="ZU8-VD-rkc">
<allowedToolbarItems>
<toolbarItem implicitItemIdentifier="NSToolbarSpaceItem" id="Iaf-zP-0J1"/>
<toolbarItem implicitItemIdentifier="NSToolbarFlexibleSpaceItem" id="xCW-4o-Rg4"/>
<toolbarItem implicitItemIdentifier="81A74327-33AB-469A-B000-7B99E478615F" label="Shuffle" paletteLabel="Shuffle" id="PKy-2S-Bpd">
<nil key="toolTip"/>
<size key="minSize" width="70" height="28"/>
<size key="maxSize" width="82" height="32"/>
<button key="view" verticalHuggingPriority="750" id="c7Y-5Y-bmU">
<rect key="frame" x="0.0" y="14" width="70" height="28"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="roundTextured" title="Shuffle" bezelStyle="texturedRounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="y9Q-xC-51x">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="shuffle:" target="Oky-zY-oP4" id="oSh-Ax-eP3"/>
</connections>
</button>
</toolbarItem>
<toolbarItem implicitItemIdentifier="46F37D49-9E53-4757-B306-C940250B5F0B" label="Search" paletteLabel="Search" id="xRY-zQ-Z0U">
<nil key="toolTip"/>
<size key="minSize" width="96" height="22"/>
<size key="maxSize" width="9999" height="22"/>
<searchField key="view" wantsLayer="YES" verticalHuggingPriority="750" id="9XO-e1-iaw">
<rect key="frame" x="0.0" y="14" width="96" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" id="N3l-Fs-c7b">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</searchFieldCell>
<connections>
<action selector="search:" target="Oky-zY-oP4" id="bkb-YO-y0f"/>
</connections>
</searchField>
</toolbarItem>
</allowedToolbarItems>
<defaultToolbarItems>
<toolbarItem reference="xRY-zQ-Z0U"/>
<toolbarItem reference="xCW-4o-Rg4"/>
<toolbarItem reference="PKy-2S-Bpd"/>
</defaultToolbarItems>
</toolbar>
</window>
<connections>
<segue destination="XfG-lQ-9wD" kind="relationship" relationship="window.shadowedContentViewController" id="cq2-FE-JQM"/>
@@ -678,38 +726,97 @@
</objects>
<point key="canvasLocation" x="75" y="250"/>
</scene>
<!--View Controller-->
<!--Users View Controller-->
<scene sceneID="hIz-AP-VOD">
<objects>
<viewController id="XfG-lQ-9wD" customClass="ViewController" customModule="IGListKitExamples" customModuleProvider="target" sceneMemberID="viewController">
<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="480" height="270"/>
<rect key="frame" x="0.0" y="0.0" width="400" height="500"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GGG-55-615">
<rect key="frame" x="116" y="126" width="248" height="17"/>
<constraints>
<constraint firstAttribute="height" constant="17" id="Slu-aL-75I"/>
<constraint firstAttribute="width" constant="244" id="hxZ-bk-Qa7"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="IGListKit macOS example. Coming soon." id="Gdi-Am-29O">
<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>
<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"/>
<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" translatesAutoresizingMaskIntoConstraints="NO" id="G7m-E1-erw">
<rect key="frame" x="0.0" y="15" width="392" height="19"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="5li-Kx-w3N">
<font key="font" metaFont="systemLight" size="16"/>
<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>
<connections>
<outlet property="dataSource" destination="XfG-lQ-9wD" id="EAO-x6-iol"/>
<outlet property="delegate" destination="XfG-lQ-9wD" id="vDv-jQ-1x9"/>
</connections>
</tableView>
</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"/>
<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"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
</subviews>
<constraints>
<constraint firstItem="GGG-55-615" firstAttribute="leading" secondItem="m2S-Jp-Qdl" secondAttribute="leading" constant="118" id="f7P-UT-nA0"/>
<constraint firstItem="GGG-55-615" firstAttribute="top" secondItem="m2S-Jp-Qdl" secondAttribute="top" constant="127" id="lkn-x9-SgV"/>
<constraint firstAttribute="bottom" secondItem="GGG-55-615" secondAttribute="bottom" constant="126" id="r0K-O8-16C"/>
<constraint firstAttribute="trailing" secondItem="GGG-55-615" secondAttribute="trailing" constant="118" id="xFP-Dx-Ljf"/>
<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"/>
</constraints>
</view>
<connections>
<outlet property="tableView" destination="cZM-LD-xWe" id="fsK-gP-bU1"/>
</connections>
</viewController>
<customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="655"/>
<point key="canvasLocation" x="75" y="923"/>
</scene>
</scenes>
</document>

View File

@@ -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"
}]

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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 })
}
}

View File

@@ -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
}
}

View File

@@ -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]
}
}