Integrating NSUserDefaults plugin to iOS (#291)

Summary:
I have a few details left, but its almost done. This PR addresses #145

- The NSUserDefaults plugin uses the SharedPreferences Desktop Part since we can reuse all of it.
- The NSUserDefaults plugin uses swizzling in order to be notified of what specific event changed at runtime.
- Added Test harness in both Sample Swift and Sample apps for iOS in order to test the plugin.
- Updated the documentation in `docs/shared-preferences-plugin.md` and` README.md`

I am open to suggestions since the desktop sharedPreferences version doesn't support deletion of preferences. Most likely I would have to modify the UI, and for that matter, I might as well build a user defaults desktop version

I wanted to add xiphirx in this MR since he developed the shared preferences plugin for Android and Desktop. I don't see a way to remove preferences from the flipper desktop app so I was wondering if you would be OK with me adding that.
Pull Request resolved: https://github.com/facebook/flipper/pull/291

Reviewed By: passy

Differential Revision: D10334685

Pulled By: priteshrnandgaonkar

fbshipit-source-id: d798c01a46df7ddecf713924799f046b560ea922
This commit is contained in:
Marc Terns
2018-10-12 04:07:39 -07:00
committed by Facebook Github Bot
parent 233b7bcd3c
commit c7ad49a9eb
21 changed files with 557 additions and 53 deletions

View File

@@ -4,9 +4,11 @@ source 'https://github.com/CocoaPods/Specs'
swift_version = "4.1"
target 'SampleSwift' do
platform :ios, '9.0'
pod 'Flipper', :path => '../../Flipper.podspec'
pod 'FlipperKit', :path => '../../FlipperKit.podspec'
pod 'FlipperKit/SKIOSNetworkPlugin', :path => '../../FlipperKit.podspec'
pod 'FlipperKit/FlipperKitUserDefaultsPlugin', :path => '../../FlipperKit.podspec'
# Layout and network plugins are not yet supported for swift projects
#pod 'SonarKit/FlipperKitLayoutComponentKitSupport', :path => '../../SonarKit.podspec'

View File

@@ -3,26 +3,28 @@ PODS:
- CocoaAsyncSocket (7.6.3)
- CocoaLibEvent (1.0.0)
- DoubleConversion (1.1.5)
- Flipper (0.7.2):
- Flipper (0.8.1):
- Folly (~> 1.1)
- RSocket (~> 0.10)
- FlipperKit (0.7.2):
- FlipperKit/Core (= 0.7.2)
- FlipperKit/Core (0.7.2):
- FlipperKit (0.8.1):
- FlipperKit/Core (= 0.8.1)
- FlipperKit/Core (0.8.1):
- CocoaAsyncSocket (~> 7.6)
- Flipper (~> 0.7.2)
- Flipper (~> 0.8.1)
- FlipperKit/CppBridge
- FlipperKit/FBCxxUtils
- FlipperKit/FBDefines
- Folly (~> 1.1)
- OpenSSL-Static (= 1.0.2.c1)
- PeerTalk (~> 0.0.2)
- FlipperKit/CppBridge (0.7.2)
- FlipperKit/FBCxxUtils (0.7.2)
- FlipperKit/FBDefines (0.7.2)
- FlipperKit/FlipperKitNetworkPlugin (0.7.2):
- FlipperKit/CppBridge (0.8.1)
- FlipperKit/FBCxxUtils (0.8.1)
- FlipperKit/FBDefines (0.8.1)
- FlipperKit/FlipperKitNetworkPlugin (0.8.1):
- FlipperKit/Core
- FlipperKit/SKIOSNetworkPlugin (0.7.2):
- FlipperKit/FlipperKitUserDefaultsPlugin (0.8.1):
- FlipperKit/Core
- FlipperKit/SKIOSNetworkPlugin (0.8.1):
- FlipperKit/Core
- FlipperKit/FlipperKitNetworkPlugin
- Folly (1.1.0):
@@ -40,6 +42,7 @@ PODS:
DEPENDENCIES:
- Flipper (from `../../Flipper.podspec`)
- FlipperKit (from `../../FlipperKit.podspec`)
- FlipperKit/FlipperKitUserDefaultsPlugin (from `../../FlipperKit.podspec`)
- FlipperKit/SKIOSNetworkPlugin (from `../../FlipperKit.podspec`)
SPEC REPOS:
@@ -50,7 +53,7 @@ SPEC REPOS:
- DoubleConversion
- glog
- OpenSSL-Static
https://github.com/facebook/Sonar.git:
https://github.com/facebook/Sonar:
- Folly
- PeerTalk
- RSocket
@@ -66,14 +69,14 @@ SPEC CHECKSUMS:
CocoaAsyncSocket: eafaa68a7e0ec99ead0a7b35015e0bf25d2c8987
CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f
DoubleConversion: e22e0762848812a87afd67ffda3998d9ef29170c
Flipper: 495fbd327298fbc55b3ff7cb399eae52c1580494
FlipperKit: a3c03ff44340fa38ff476f6269484270533c5749
Flipper: 847a9ba40de727f6bb02a1b365674538f051ecf2
FlipperKit: 93c4d1c38f10ef2900f646ddb65c29bdf4975cfd
Folly: dd92f69322d8535b9df98d7c91e442b0fce2fff2
glog: 1de0bb937dccdc981596d3b5825ebfb765017ded
OpenSSL-Static: bd17e34564a8591ad76b740318683a6caa19a13e
PeerTalk: 77481b0a8136f226b90ccf828d6061f70139ffde
RSocket: 4fdb7e562db30a2d4fceddefdc601749ffc9ebe2
PODFILE CHECKSUM: 74eec891e5ca29c4d3817bc4c2c8ed86c6b9958c
PODFILE CHECKSUM: 3eeb7fcb7e93b388b33cfa6730390259dd6292f7
COCOAPODS: 1.5.3

View File

@@ -7,8 +7,9 @@
objects = {
/* Begin PBXBuildFile section */
322121E8134CB1102F388691 /* libPods-SampleSwift.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6233B2BCBACBB2E8D715CC04 /* libPods-SampleSwift.a */; };
4E102343216ADB5900160734 /* UserDefaultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E102342216ADB5900160734 /* UserDefaultsViewController.swift */; };
532FF2DF211316ED00FC5A10 /* NetworkViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 532FF2DE211316ED00FC5A10 /* NetworkViewController.swift */; };
629899EF3F77DD9CC9462399 /* libPods-SampleSwift.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 54D075F591C612F4A46C4C50 /* libPods-SampleSwift.a */; };
A19C402720E20023004BF1F7 /* Icons.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A19C402620E20023004BF1F7 /* Icons.xcassets */; };
A1EC522D20DED61B007C6977 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1EC522C20DED61B007C6977 /* AppDelegate.swift */; };
A1EC522F20DED61B007C6977 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1EC522E20DED61B007C6977 /* ViewController.swift */; };
@@ -17,10 +18,11 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
4E102342216ADB5900160734 /* UserDefaultsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsViewController.swift; sourceTree = "<group>"; };
532FF2DE211316ED00FC5A10 /* NetworkViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkViewController.swift; sourceTree = "<group>"; };
54D075F591C612F4A46C4C50 /* libPods-SampleSwift.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SampleSwift.a"; sourceTree = BUILT_PRODUCTS_DIR; };
59BC319BD0C305C87CCBB954 /* Pods-SampleSwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleSwift.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SampleSwift/Pods-SampleSwift.debug.xcconfig"; sourceTree = "<group>"; };
76A1EA0FA19A4343BF5468B9 /* Pods-SampleSwift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleSwift.release.xcconfig"; path = "Pods/Target Support Files/Pods-SampleSwift/Pods-SampleSwift.release.xcconfig"; sourceTree = "<group>"; };
6233B2BCBACBB2E8D715CC04 /* libPods-SampleSwift.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SampleSwift.a"; sourceTree = BUILT_PRODUCTS_DIR; };
82933F6ACB19D9FAA4A0B0DC /* Pods-SampleSwift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleSwift.release.xcconfig"; path = "Pods/Target Support Files/Pods-SampleSwift/Pods-SampleSwift.release.xcconfig"; sourceTree = "<group>"; };
871A353CDDB4CD0F4FE9A564 /* Pods-SampleSwift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleSwift.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SampleSwift/Pods-SampleSwift.debug.xcconfig"; sourceTree = "<group>"; };
A19C402620E20023004BF1F7 /* Icons.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Icons.xcassets; sourceTree = SOURCE_ROOT; };
A1EC522920DED61B007C6977 /* SampleSwift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleSwift.app; sourceTree = BUILT_PRODUCTS_DIR; };
A1EC522C20DED61B007C6977 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@@ -35,20 +37,28 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
629899EF3F77DD9CC9462399 /* libPods-SampleSwift.a in Frameworks */,
322121E8134CB1102F388691 /* libPods-SampleSwift.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
690EC1830793DEC228432979 /* Frameworks */ = {
isa = PBXGroup;
children = (
6233B2BCBACBB2E8D715CC04 /* libPods-SampleSwift.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
A1EC522020DED61B007C6977 = {
isa = PBXGroup;
children = (
A1EC522B20DED61B007C6977 /* SampleSwift */,
A1EC522A20DED61B007C6977 /* Products */,
D64C1609A23F7F455EB46170 /* Pods */,
FCE001267EA01B99C44EBA11 /* Frameworks */,
B8EB61E9CA5FC85959AD5F3E /* Pods */,
690EC1830793DEC228432979 /* Frameworks */,
);
sourceTree = "<group>";
};
@@ -70,27 +80,20 @@
A1EC523520DED61C007C6977 /* LaunchScreen.storyboard */,
A1EC523820DED61C007C6977 /* Info.plist */,
532FF2DE211316ED00FC5A10 /* NetworkViewController.swift */,
4E102342216ADB5900160734 /* UserDefaultsViewController.swift */,
);
path = SampleSwift;
sourceTree = "<group>";
};
D64C1609A23F7F455EB46170 /* Pods */ = {
B8EB61E9CA5FC85959AD5F3E /* Pods */ = {
isa = PBXGroup;
children = (
59BC319BD0C305C87CCBB954 /* Pods-SampleSwift.debug.xcconfig */,
76A1EA0FA19A4343BF5468B9 /* Pods-SampleSwift.release.xcconfig */,
871A353CDDB4CD0F4FE9A564 /* Pods-SampleSwift.debug.xcconfig */,
82933F6ACB19D9FAA4A0B0DC /* Pods-SampleSwift.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
FCE001267EA01B99C44EBA11 /* Frameworks */ = {
isa = PBXGroup;
children = (
54D075F591C612F4A46C4C50 /* libPods-SampleSwift.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -98,7 +101,7 @@
isa = PBXNativeTarget;
buildConfigurationList = A1EC523B20DED61C007C6977 /* Build configuration list for PBXNativeTarget "SampleSwift" */;
buildPhases = (
FEB6C3EADB1D3B088347CC11 /* [CP] Check Pods Manifest.lock */,
D9A91DD7C5294D8508D0A168 /* [CP] Check Pods Manifest.lock */,
A1EC522520DED61B007C6977 /* Sources */,
A1EC522620DED61B007C6977 /* Frameworks */,
A1EC522720DED61B007C6977 /* Resources */,
@@ -159,7 +162,7 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
FEB6C3EADB1D3B088347CC11 /* [CP] Check Pods Manifest.lock */ = {
D9A91DD7C5294D8508D0A168 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -186,6 +189,7 @@
files = (
A1EC522F20DED61B007C6977 /* ViewController.swift in Sources */,
532FF2DF211316ED00FC5A10 /* NetworkViewController.swift in Sources */,
4E102343216ADB5900160734 /* UserDefaultsViewController.swift in Sources */,
A1EC522D20DED61B007C6977 /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -320,7 +324,7 @@
};
A1EC523C20DED61C007C6977 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 59BC319BD0C305C87CCBB954 /* Pods-SampleSwift.debug.xcconfig */;
baseConfigurationReference = 871A353CDDB4CD0F4FE9A564 /* Pods-SampleSwift.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
@@ -384,7 +388,7 @@
};
A1EC523D20DED61C007C6977 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 76A1EA0FA19A4343BF5468B9 /* Pods-SampleSwift.release.xcconfig */;
baseConfigurationReference = 82933F6ACB19D9FAA4A0B0DC /* Pods-SampleSwift.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;

View File

@@ -19,6 +19,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// client.addPlugin(FlipperKitLayoutPlugin(rootNode: application, descriptorMapper: layoutDescriptorMapper))
client?.add(FlipperKitNetworkPlugin(networkAdapter: SKIOSNetworkAdapter()))
client?.add(FKUserDefaultsPlugin.init(suiteName: nil))
client?.start()
let storyboard = UIStoryboard(name: "MainStoryBoard", bundle: nil)

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.14"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@@ -45,17 +45,34 @@
<color key="titleColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fNm-CZ-QXA">
<rect key="frame" x="0.0" y="156" width="375" height="60"/>
<color key="backgroundColor" red="0.89411764709999997" green="0.87450980389999999" blue="0.92941176469999998" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="60" id="npr-g6-NHN"/>
<constraint firstAttribute="height" constant="60" id="rC6-NM-7tF"/>
</constraints>
<state key="normal" title="User Defaults">
<color key="titleColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
<connections>
<segue destination="qXL-iV-l3f" kind="show" id="ubf-Kh-d0a"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="xh0-Q4-60g" firstAttribute="trailing" secondItem="fhd-5r-dZW" secondAttribute="trailing" id="0Uf-Hu-a0E"/>
<constraint firstItem="xh0-Q4-60g" firstAttribute="top" secondItem="R0E-21-TJB" secondAttribute="bottom" constant="8" id="1LN-rK-SJg"/>
<constraint firstItem="fNm-CZ-QXA" firstAttribute="trailing" secondItem="xh0-Q4-60g" secondAttribute="trailing" id="4Dh-7z-set"/>
<constraint firstItem="R0E-21-TJB" firstAttribute="top" secondItem="fhd-5r-dZW" secondAttribute="top" id="4Og-b3-TDg"/>
<constraint firstItem="fNm-CZ-QXA" firstAttribute="leading" secondItem="xh0-Q4-60g" secondAttribute="leading" id="61y-q6-PKw"/>
<constraint firstItem="R0E-21-TJB" firstAttribute="leading" secondItem="fhd-5r-dZW" secondAttribute="leading" id="7mG-cL-FY7"/>
<constraint firstItem="xh0-Q4-60g" firstAttribute="leading" secondItem="fhd-5r-dZW" secondAttribute="leading" id="DPX-l5-Rca"/>
<constraint firstItem="B57-dd-H6T" firstAttribute="leading" secondItem="fhd-5r-dZW" secondAttribute="leading" id="Hhd-in-vfk"/>
<constraint firstItem="fhd-5r-dZW" firstAttribute="trailing" secondItem="B57-dd-H6T" secondAttribute="trailing" id="ODW-CJ-ean"/>
<constraint firstItem="fhd-5r-dZW" firstAttribute="trailing" secondItem="R0E-21-TJB" secondAttribute="trailing" id="QyG-ez-XwS"/>
<constraint firstItem="fNm-CZ-QXA" firstAttribute="top" secondItem="xh0-Q4-60g" secondAttribute="bottom" constant="8" id="jpH-cn-dRV"/>
<constraint firstItem="fhd-5r-dZW" firstAttribute="bottom" secondItem="B57-dd-H6T" secondAttribute="bottom" id="kh1-ra-tzR"/>
<constraint firstItem="B57-dd-H6T" firstAttribute="top" secondItem="fhd-5r-dZW" secondAttribute="top" id="vGA-oo-EGc"/>
</constraints>
@@ -156,8 +173,78 @@
</objects>
<point key="canvasLocation" x="958" y="280"/>
</scene>
<!--User Defaults View Controller-->
<scene sceneID="Lf5-Is-foC">
<objects>
<viewController storyboardIdentifier="UserDefaultsViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" id="qXL-iV-l3f" customClass="UserDefaultsViewController" customModule="SampleSwift" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="d4y-bZ-bMk">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="sonarpattern" translatesAutoresizingMaskIntoConstraints="NO" id="NwP-P2-2qI">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
</imageView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="mbW-th-h0H">
<rect key="frame" x="40" y="366" width="295" height="40"/>
<color key="backgroundColor" red="0.89411764709999997" green="0.87450980389999999" blue="0.92941176469999998" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
<constraints>
<constraint firstAttribute="height" constant="40" id="2Ck-uX-U8W"/>
<constraint firstAttribute="width" constant="295" id="Vm5-KP-Lin"/>
</constraints>
<state key="normal" title="Save">
<color key="titleColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
<connections>
<action selector="tappedSave:" destination="qXL-iV-l3f" eventType="touchUpInside" id="2ny-9O-idj"/>
</connections>
</button>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="key (cannot be nil)" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="th9-uR-BZx">
<rect key="frame" x="39.5" y="328.5" width="296" height="30"/>
<constraints>
<constraint firstAttribute="width" constant="296" id="8Vb-8W-w9l"/>
<constraint firstAttribute="height" constant="30" id="Zt3-fY-1us"/>
</constraints>
<nil key="textColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
</textField>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="String Value..." textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="lZu-0P-qN9">
<rect key="frame" x="40" y="290" width="295" height="30"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="57Z-7r-viw"/>
<constraint firstAttribute="width" constant="295" id="9Ch-56-UaE"/>
</constraints>
<nil key="textColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
</textField>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="lZu-0P-qN9" firstAttribute="centerX" secondItem="NwP-P2-2qI" secondAttribute="centerX" id="8jU-Y6-In5"/>
<constraint firstItem="NwP-P2-2qI" firstAttribute="leading" secondItem="Tub-Io-dFp" secondAttribute="leading" id="CZ3-Tg-feY"/>
<constraint firstItem="mbW-th-h0H" firstAttribute="centerX" secondItem="NwP-P2-2qI" secondAttribute="centerX" id="EwC-G5-ovu"/>
<constraint firstItem="Tub-Io-dFp" firstAttribute="bottom" secondItem="NwP-P2-2qI" secondAttribute="bottom" id="Gu4-Iw-2FS"/>
<constraint firstItem="NwP-P2-2qI" firstAttribute="top" secondItem="Tub-Io-dFp" secondAttribute="top" id="Klq-9a-aCG"/>
<constraint firstItem="th9-uR-BZx" firstAttribute="centerY" secondItem="NwP-P2-2qI" secondAttribute="centerY" id="WSs-Qk-Ted"/>
<constraint firstItem="mbW-th-h0H" firstAttribute="top" secondItem="th9-uR-BZx" secondAttribute="bottom" constant="7.5" id="aJD-vA-qjO"/>
<constraint firstItem="th9-uR-BZx" firstAttribute="top" secondItem="lZu-0P-qN9" secondAttribute="bottom" constant="8.5" id="ky1-5U-S5x"/>
<constraint firstItem="th9-uR-BZx" firstAttribute="centerX" secondItem="NwP-P2-2qI" secondAttribute="centerX" id="yTa-ic-pLV"/>
<constraint firstItem="Tub-Io-dFp" firstAttribute="trailing" secondItem="NwP-P2-2qI" secondAttribute="trailing" id="zZm-qm-nCb"/>
</constraints>
<viewLayoutGuide key="safeArea" id="Tub-Io-dFp"/>
</view>
<connections>
<outlet property="keyTextField" destination="th9-uR-BZx" id="Hye-pZ-fdM"/>
<outlet property="valueTextField" destination="lZu-0P-qN9" id="Fya-Kj-t0V"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Trg-qn-c4O" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1613.5999999999999" y="278.41079460269867"/>
</scene>
</scenes>
<resources>
<image name="sonarpattern" width="421" height="658"/>
<image name="sonarpattern" width="843" height="1317"/>
</resources>
</document>

View File

@@ -0,0 +1,26 @@
//
// UserDefaultsViewController.swift
// SampleSwift
//
// Created by Marc Terns on 10/7/18.
// Copyright © 2018 Noah Gilmore. All rights reserved.
//
import UIKit
class UserDefaultsViewController: UIViewController {
@IBOutlet weak var keyTextField: UITextField!
@IBOutlet weak var valueTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func tappedSave(_ sender: Any) {
UserDefaults.standard.set(self.valueTextField.text ?? "", forKey: self.keyTextField.text ?? "")
}
}