Convertes to Swift and SwiftUI

This commit is contained in:
2021-01-16 01:12:49 -08:00
parent 3d54342687
commit f6ef6a7f3d
459 changed files with 2301 additions and 23910 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "self:MonsterCards.xcodeproj">
location = "self:">
</FileRef>
</Workspace>

View File

@@ -1,98 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E2F7246F25005E89007D87ED"
BuildableName = "Monster Cards.app"
BlueprintName = "MonsterCards"
ReferencedContainer = "container:MonsterCards.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E2F7249025005E8A007D87ED"
BuildableName = "MonsterCardsTests.xctest"
BlueprintName = "MonsterCardsTests"
ReferencedContainer = "container:MonsterCards.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E2F7249B25005E8A007D87ED"
BuildableName = "MonsterCardsUITests.xctest"
BlueprintName = "MonsterCardsUITests"
ReferencedContainer = "container:MonsterCards.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E2F7246F25005E89007D87ED"
BuildableName = "Monster Cards.app"
BlueprintName = "MonsterCards"
ReferencedContainer = "container:MonsterCards.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E2F7246F25005E89007D87ED"
BuildableName = "Monster Cards.app"
BlueprintName = "MonsterCards"
ReferencedContainer = "container:MonsterCards.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:MonsterCards.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -1,20 +0,0 @@
//
// AppDelegate.h
// MonsterCards
//
// Created by Tom Hicks on 9/2/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (readonly, strong) NSPersistentCloudKitContainer *persistentContainer;
- (void)saveContext;
@end

View File

@@ -1,86 +0,0 @@
//
// AppDelegate.m
// MonsterCards
//
// Created by Tom Hicks on 9/2/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "AppDelegate.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
return YES;
}
#pragma mark - UISceneSession lifecycle
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
}
- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
#pragma mark - Core Data stack
@synthesize persistentContainer = _persistentContainer;
- (NSPersistentCloudKitContainer *)persistentContainer {
// The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it.
@synchronized (self) {
if (_persistentContainer == nil) {
_persistentContainer = [[NSPersistentCloudKitContainer alloc] initWithName:@"MonsterCards"];
[_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
if (error != nil) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
NSLog(@"Unresolved error %@, %@", error, error.userInfo);
abort();
}
}];
}
}
return _persistentContainer;
}
#pragma mark - Core Data Saving support
- (void)saveContext {
NSManagedObjectContext *context = self.persistentContainer.viewContext;
NSError *error = nil;
if ([context hasChanges] && ![context save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(@"Unresolved error %@, %@", error, error.userInfo);
abort();
}
}
@end

View File

@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "section-divider.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "section-divider@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "section-divider@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

Before

Width:  |  Height:  |  Size: 338 B

After

Width:  |  Height:  |  Size: 338 B

View File

Before

Width:  |  Height:  |  Size: 1001 B

After

Width:  |  Height:  |  Size: 1001 B

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>

View File

@@ -1,681 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="49e-Tb-3d3">
<device id="ipad11_0rounded" orientation="landscape" layout="fullscreen" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Tab Bar Controller-->
<scene sceneID="yl2-sM-qoP">
<objects>
<tabBarController id="49e-Tb-3d3" sceneMemberID="viewController">
<tabBar key="tabBar" contentMode="scaleToFill" id="W28-zg-YXA">
<rect key="frame" x="0.0" y="975" width="768" height="49"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
</tabBar>
<connections>
<segue destination="ymH-E2-afX" kind="relationship" relationship="viewControllers" id="aRQ-Ld-n77"/>
<segue destination="giS-lS-9u3" kind="relationship" relationship="viewControllers" id="QFA-Ya-tDm"/>
<segue destination="hrp-Ji-OWx" kind="relationship" relationship="viewControllers" id="OpL-Bq-zuh"/>
<segue destination="k1r-8K-fba" kind="relationship" relationship="viewControllers" id="tX1-v4-ah1"/>
</connections>
</tabBarController>
<placeholder placeholderIdentifier="IBFirstResponder" id="HuB-VB-40B" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-1197" y="135"/>
</scene>
<!--Dashboard-->
<scene sceneID="qOG-lF-VxJ">
<objects>
<viewController id="giS-lS-9u3" customClass="DashboardViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="JUJ-wP-ffb">
<rect key="frame" x="0.0" y="0.0" width="1194" height="834"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<viewLayoutGuide key="safeArea" id="QQV-YX-2Yb"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
<tabBarItem key="tabBarItem" title="Dashboard" image="rectangle.3.offgrid.fill" catalog="system" id="wgb-7v-3jq"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="onm-5g-reZ" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="622" y="-243"/>
</scene>
<!--Collections-->
<scene sceneID="7BQ-Kv-Tfd">
<objects>
<viewController id="hrp-Ji-OWx" customClass="CollectionsViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="j0E-Ka-SnM">
<rect key="frame" x="0.0" y="0.0" width="1194" height="834"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<viewLayoutGuide key="safeArea" id="Rkq-9l-CBb"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
<tabBarItem key="tabBarItem" title="Collections" image="tray.full.fill" catalog="system" id="7og-Zf-zGt"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="gar-xb-BMe" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="622" y="513"/>
</scene>
<!--Search View Controller-->
<scene sceneID="gMb-gI-y2F">
<objects>
<tableViewController id="WmO-9m-qPj" customClass="SearchViewController" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="oCq-Hl-UA9">
<rect key="frame" x="0.0" y="0.0" width="1194" height="834"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<searchBar key="tableHeaderView" contentMode="redraw" text="" id="fQb-XL-QZB">
<rect key="frame" x="0.0" y="0.0" width="1554" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<textInputTraits key="textInputTraits"/>
<connections>
<outlet property="delegate" destination="WmO-9m-qPj" id="z6d-4x-mQ2"/>
</connections>
</searchBar>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="MonsterCell" id="T01-Aw-boG">
<rect key="frame" x="0.0" y="72" width="1194" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="T01-Aw-boG" id="eV3-pN-I0b">
<rect key="frame" x="0.0" y="0.0" width="1194" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
<connections>
<segue destination="2B6-hx-mku" kind="show" identifier="ShowMonsterDetail" id="Ob1-Gh-ZDQ"/>
</connections>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="WmO-9m-qPj" id="g6M-G6-7uY"/>
<outlet property="delegate" destination="WmO-9m-qPj" id="tXc-lT-yN2"/>
</connections>
</tableView>
<navigationItem key="navigationItem" id="iaa-fm-F6D"/>
<simulatedTabBarMetrics key="simulatedBottomBarMetrics"/>
<connections>
<outlet property="searchBar" destination="fQb-XL-QZB" id="Xxv-bW-N10"/>
<outlet property="searchResults" destination="oCq-Hl-UA9" id="zpM-Yi-UyD"/>
</connections>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="pju-ns-0Vf" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1532" y="-999"/>
</scene>
<!--Search-->
<scene sceneID="CGm-bP-IV8">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="ymH-E2-afX" sceneMemberID="viewController">
<tabBarItem key="tabBarItem" title="Search" image="magnifyingglass" catalog="system" id="pkF-hG-DTJ"/>
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="0jh-Lf-VY3">
<rect key="frame" x="0.0" y="0.0" width="1194" height="50"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<nil name="viewControllers"/>
<connections>
<segue destination="WmO-9m-qPj" kind="relationship" relationship="rootViewController" id="cee-Qb-RoS"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="eQU-y1-4NN" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="622" y="-999"/>
</scene>
<!--Library-->
<scene sceneID="5mp-gP-3px">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="k1r-8K-fba" sceneMemberID="viewController">
<tabBarItem key="tabBarItem" title="Library" image="book.fill" catalog="system" id="Kz3-Xe-dOi"/>
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="MGB-oQ-Uth">
<rect key="frame" x="0.0" y="0.0" width="1194" height="50"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<nil name="viewControllers"/>
<connections>
<segue destination="5c8-8y-QZ5" kind="relationship" relationship="rootViewController" id="OAU-YH-mgF"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="vS1-d2-ni5" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="622" y="1269"/>
</scene>
<!--Library-->
<scene sceneID="bsS-Pz-KZX">
<objects>
<tableViewController id="5c8-8y-QZ5" customClass="LibraryViewController" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="NgM-um-5Ur">
<rect key="frame" x="0.0" y="0.0" width="1194" height="834"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="MonsterCell" id="Boi-md-853">
<rect key="frame" x="0.0" y="28" width="1194" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Boi-md-853" id="o5t-lM-bf4">
<rect key="frame" x="0.0" y="0.0" width="1194" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
<connections>
<segue destination="2B6-hx-mku" kind="show" identifier="ShowMonsterDetail" id="W6E-7q-Wzb"/>
</connections>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="5c8-8y-QZ5" id="xtC-Yg-dwQ"/>
<outlet property="delegate" destination="5c8-8y-QZ5" id="6wi-Pi-KwG"/>
</connections>
</tableView>
<navigationItem key="navigationItem" title="Library" id="MnW-A3-Ua9">
<barButtonItem key="rightBarButtonItem" title="Item" image="plus" catalog="system" id="qDP-Tb-aMi">
<connections>
<action selector="addNewMonster:" destination="5c8-8y-QZ5" id="pQe-rL-fqA"/>
</connections>
</barButtonItem>
</navigationItem>
<connections>
<outlet property="monstersTable" destination="NgM-um-5Ur" id="HFK-4U-MeQ"/>
</connections>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="32A-4i-7h9" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1532" y="1269"/>
</scene>
<!--Monster View Controller-->
<scene sceneID="b1L-lx-eFe">
<objects>
<viewController id="2B6-hx-mku" customClass="MonsterViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="skY-EN-baw">
<rect key="frame" x="0.0" y="0.0" width="1194" height="834"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pZa-ia-7UT">
<rect key="frame" x="8" y="58" width="1178" height="14.5"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" systemColor="secondaryLabelColor"/>
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="section-divider" translatesAutoresizingMaskIntoConstraints="NO" id="WYM-ya-Yje">
<rect key="frame" x="8" y="80.5" width="1178" height="10"/>
<constraints>
<constraint firstAttribute="height" constant="10" id="1ld-SE-NlL"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p05-uG-AlV">
<rect key="frame" x="8" y="98.5" width="1178" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ae7-YW-3xk">
<rect key="frame" x="8" y="123.5" width="1178" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0HK-T4-KQW" userLabel="Monster Speed">
<rect key="frame" x="8" y="148.5" width="1178" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="section-divider" translatesAutoresizingMaskIntoConstraints="NO" id="8yl-Bh-hgf">
<rect key="frame" x="8" y="173.5" width="1178" height="10"/>
<constraints>
<constraint firstAttribute="height" constant="10" id="93X-Qh-bnv"/>
</constraints>
</imageView>
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" translatesAutoresizingMaskIntoConstraints="NO" id="lBc-X0-1US" userLabel="Ability Scores">
<rect key="frame" x="0.0" y="191.5" width="1194" height="29"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="rKw-op-tu1" userLabel="Strength">
<rect key="frame" x="0.0" y="0.0" width="199" height="29"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="STR" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sJQ-m8-4rU" userLabel="STR">
<rect key="frame" x="87" y="0.0" width="25.5" height="14.5"/>
<fontDescription key="fontDescription" type="system" weight="heavy" pointSize="12"/>
<color key="textColor" red="0.60784313729999995" green="0.15686274510000001" blue="0.094117647060000004" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Gfb-yf-Tbu" userLabel="StrScore">
<rect key="frame" x="84" y="14.5" width="31" height="14.5"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" red="0.60784313729999995" green="0.15686274510000001" blue="0.094117647060000004" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="odM-CK-6Gr" userLabel="Dexterity">
<rect key="frame" x="199" y="0.0" width="199" height="29"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="DEX" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OkX-5Z-BLZ" userLabel="DEX">
<rect key="frame" x="86.5" y="0.0" width="26" height="14.5"/>
<fontDescription key="fontDescription" type="system" weight="heavy" pointSize="12"/>
<color key="textColor" red="0.60784313729999995" green="0.15686274510000001" blue="0.094117647060000004" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1y6-3k-jnN" userLabel="DexScore">
<rect key="frame" x="84" y="14.5" width="31" height="14.5"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" red="0.60784313729999995" green="0.15686274510000001" blue="0.094117647060000004" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="8zm-3f-c3k" userLabel="Constitution">
<rect key="frame" x="398" y="0.0" width="199" height="29"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="CON" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="H73-4Y-0aY" userLabel="CON">
<rect key="frame" x="85.5" y="0.0" width="28" height="14.5"/>
<fontDescription key="fontDescription" type="system" weight="heavy" pointSize="12"/>
<color key="textColor" red="0.60784313729999995" green="0.15686274510000001" blue="0.094117647060000004" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Koq-7S-VQO" userLabel="ConScore">
<rect key="frame" x="84" y="14.5" width="31" height="14.5"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" red="0.60784313729999995" green="0.15686274510000001" blue="0.094117647060000004" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="Ebv-nS-bwu" userLabel="Intelligence">
<rect key="frame" x="597" y="0.0" width="199" height="29"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="INT" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sRO-Ma-Wtu" userLabel="INT">
<rect key="frame" x="88.5" y="0.0" width="22" height="14.5"/>
<fontDescription key="fontDescription" type="system" weight="heavy" pointSize="12"/>
<color key="textColor" red="0.60784313729999995" green="0.15686274510000001" blue="0.094117647060000004" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p82-wJ-OUJ" userLabel="IntScore">
<rect key="frame" x="84" y="14.5" width="31" height="14.5"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" red="0.60784313729999995" green="0.15686274510000001" blue="0.094117647060000004" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="6Ba-BF-Q69" userLabel="Wisdom">
<rect key="frame" x="796" y="0.0" width="199" height="29"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="WIS" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="63d-Ej-rif" userLabel="WIS">
<rect key="frame" x="87" y="0.0" width="25" height="14.5"/>
<fontDescription key="fontDescription" type="system" weight="heavy" pointSize="12"/>
<color key="textColor" red="0.60784313729999995" green="0.15686274510000001" blue="0.094117647060000004" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0lo-zE-s25" userLabel="WisScore">
<rect key="frame" x="84" y="14.5" width="31" height="14.5"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" red="0.60784313729999995" green="0.15686274510000001" blue="0.094117647060000004" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="o2g-6F-Qaj" userLabel="Charisma">
<rect key="frame" x="995" y="0.0" width="199" height="29"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="CHA" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7ni-qo-7DG" userLabel="CHA">
<rect key="frame" x="85.5" y="0.0" width="28" height="14.5"/>
<fontDescription key="fontDescription" type="system" weight="heavy" pointSize="12"/>
<color key="textColor" red="0.60784313729999995" green="0.15686274510000001" blue="0.094117647060000004" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gz5-gL-ybS" userLabel="ChaScore">
<rect key="frame" x="84" y="14.5" width="31" height="14.5"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" red="0.60784313729999995" green="0.15686274510000001" blue="0.094117647060000004" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
</subviews>
</stackView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="section-divider" translatesAutoresizingMaskIntoConstraints="NO" id="zcw-26-kZQ">
<rect key="frame" x="8" y="228.5" width="1178" height="10"/>
<constraints>
<constraint firstAttribute="height" constant="10" id="mAQ-Ry-dzJ"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hMb-Iw-k4h">
<rect key="frame" x="8" y="246.5" width="1178" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<viewLayoutGuide key="safeArea" id="WIX-Yu-LXJ"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="pZa-ia-7UT" firstAttribute="leading" secondItem="WYM-ya-Yje" secondAttribute="leading" id="5Uq-aL-jK9"/>
<constraint firstItem="8yl-Bh-hgf" firstAttribute="centerX" secondItem="lBc-X0-1US" secondAttribute="centerX" id="6Lc-MB-cog"/>
<constraint firstItem="WIX-Yu-LXJ" firstAttribute="trailing" secondItem="hMb-Iw-k4h" secondAttribute="trailing" constant="8" id="7Ti-ID-py9"/>
<constraint firstItem="p05-uG-AlV" firstAttribute="trailing" secondItem="Ae7-YW-3xk" secondAttribute="trailing" id="DpK-wf-8qN"/>
<constraint firstItem="pZa-ia-7UT" firstAttribute="top" secondItem="WIX-Yu-LXJ" secondAttribute="top" constant="8" id="DpT-G9-lVC"/>
<constraint firstItem="pZa-ia-7UT" firstAttribute="trailing" secondItem="WYM-ya-Yje" secondAttribute="trailing" id="GdO-iX-qNf"/>
<constraint firstItem="lBc-X0-1US" firstAttribute="top" secondItem="8yl-Bh-hgf" secondAttribute="bottom" constant="8" symbolic="YES" id="Jva-26-0HW"/>
<constraint firstItem="8yl-Bh-hgf" firstAttribute="top" secondItem="0HK-T4-KQW" secondAttribute="bottom" constant="8" symbolic="YES" id="KEh-c7-PzK"/>
<constraint firstItem="8yl-Bh-hgf" firstAttribute="trailing" secondItem="zcw-26-kZQ" secondAttribute="trailing" id="KIJ-Ng-1ud"/>
<constraint firstItem="Ae7-YW-3xk" firstAttribute="trailing" secondItem="0HK-T4-KQW" secondAttribute="trailing" id="MCN-A6-9Zy"/>
<constraint firstItem="8yl-Bh-hgf" firstAttribute="leading" secondItem="zcw-26-kZQ" secondAttribute="leading" id="Mdw-9f-Xki"/>
<constraint firstItem="0HK-T4-KQW" firstAttribute="top" secondItem="Ae7-YW-3xk" secondAttribute="bottom" constant="8" symbolic="YES" id="NN6-F1-KAA"/>
<constraint firstItem="pZa-ia-7UT" firstAttribute="leading" secondItem="WIX-Yu-LXJ" secondAttribute="leading" constant="8" id="Pzo-wK-eiz"/>
<constraint firstItem="WYM-ya-Yje" firstAttribute="trailing" secondItem="p05-uG-AlV" secondAttribute="trailing" id="QYF-YD-yAJ"/>
<constraint firstItem="0HK-T4-KQW" firstAttribute="leading" secondItem="8yl-Bh-hgf" secondAttribute="leading" id="Zyv-3S-cKO"/>
<constraint firstItem="hMb-Iw-k4h" firstAttribute="leading" secondItem="WIX-Yu-LXJ" secondAttribute="leading" constant="8" id="cXv-Yu-4Za"/>
<constraint firstItem="Ae7-YW-3xk" firstAttribute="top" secondItem="p05-uG-AlV" secondAttribute="bottom" constant="8" symbolic="YES" id="cmv-YY-dax"/>
<constraint firstItem="lBc-X0-1US" firstAttribute="leading" secondItem="skY-EN-baw" secondAttribute="leading" id="dPJ-04-kH2"/>
<constraint firstItem="0HK-T4-KQW" firstAttribute="trailing" secondItem="8yl-Bh-hgf" secondAttribute="trailing" id="dTU-0J-ILa"/>
<constraint firstItem="WYM-ya-Yje" firstAttribute="leading" secondItem="p05-uG-AlV" secondAttribute="leading" id="ds0-tz-itq"/>
<constraint firstItem="Ae7-YW-3xk" firstAttribute="leading" secondItem="0HK-T4-KQW" secondAttribute="leading" id="n28-CV-L8l"/>
<constraint firstItem="p05-uG-AlV" firstAttribute="leading" secondItem="Ae7-YW-3xk" secondAttribute="leading" id="nHB-QQ-JPT"/>
<constraint firstItem="zcw-26-kZQ" firstAttribute="top" secondItem="lBc-X0-1US" secondAttribute="bottom" constant="8" symbolic="YES" id="q0O-jK-tzU"/>
<constraint firstAttribute="trailing" secondItem="lBc-X0-1US" secondAttribute="trailing" id="sTY-zt-Sac"/>
<constraint firstItem="hMb-Iw-k4h" firstAttribute="top" secondItem="zcw-26-kZQ" secondAttribute="bottom" constant="8" id="sk5-ZE-Vfd"/>
<constraint firstItem="WYM-ya-Yje" firstAttribute="top" secondItem="pZa-ia-7UT" secondAttribute="bottom" constant="8" symbolic="YES" id="thF-cF-gcb"/>
<constraint firstItem="p05-uG-AlV" firstAttribute="top" secondItem="WYM-ya-Yje" secondAttribute="bottom" constant="8" symbolic="YES" id="ydJ-o0-UE4"/>
</constraints>
</view>
<navigationItem key="navigationItem" id="U0U-wB-eVJ">
<barButtonItem key="rightBarButtonItem" title="Edit" id="JQh-6t-vKa">
<connections>
<segue destination="9au-4W-gkk" kind="show" identifier="EditMonster" id="h6n-Yx-e51"/>
</connections>
</barButtonItem>
</navigationItem>
<connections>
<outlet property="monsterArmorClass" destination="p05-uG-AlV" id="ISg-8R-AnX"/>
<outlet property="monsterCharisma" destination="gz5-gL-ybS" id="OIf-em-3nf"/>
<outlet property="monsterConstitution" destination="Koq-7S-VQO" id="Y6J-nO-R5x"/>
<outlet property="monsterDexterity" destination="1y6-3k-jnN" id="6rv-Fm-Jbt"/>
<outlet property="monsterHitPoints" destination="Ae7-YW-3xk" id="3og-CQ-jGe"/>
<outlet property="monsterIntelligence" destination="p82-wJ-OUJ" id="YzY-1R-Bje"/>
<outlet property="monsterMeta" destination="pZa-ia-7UT" id="QEV-cs-IEk"/>
<outlet property="monsterSavingThrows" destination="hMb-Iw-k4h" id="LYK-xX-GCc"/>
<outlet property="monsterSpeed" destination="0HK-T4-KQW" id="V8T-ZK-2aB"/>
<outlet property="monsterStrength" destination="Gfb-yf-Tbu" id="obD-QK-ZSo"/>
<outlet property="monsterWisdom" destination="0lo-zE-s25" id="Wy8-kG-8Gg"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="lvO-c7-FKV" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="2441" y="1269"/>
</scene>
<!--Edit Monster-->
<scene sceneID="Pm6-vB-wuj">
<objects>
<viewController id="9au-4W-gkk" customClass="EditMonsterViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="0ji-9h-I5j">
<rect key="frame" x="0.0" y="0.0" width="1194" height="834"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="Ph7-w5-jbm">
<rect key="frame" x="0.0" y="50" width="1194" height="719"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="MCRadioField" id="iSG-Tv-aCX" userLabel="MCRadioField" customClass="MCRadioFieldTableViewCell">
<rect key="frame" x="0.0" y="28" width="1194" height="92"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="iSG-Tv-aCX" id="scU-nK-qiI">
<rect key="frame" x="0.0" y="0.0" width="1194" height="92"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="nTa-ey-RFM">
<rect key="frame" x="20" y="20" width="1154" height="52"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="clip" baselineAdjustment="alignBaselines" minimumScaleFactor="0.25" translatesAutoresizingMaskIntoConstraints="NO" id="Sap-Qr-1ZU">
<rect key="frame" x="0.0" y="0.0" width="1154" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="rJq-eZ-wvm">
<rect key="frame" x="0.0" y="21" width="1154" height="32"/>
<segments>
<segment title="First"/>
<segment title="Second"/>
</segments>
<connections>
<action selector="selectedSegmentChanged:" destination="iSG-Tv-aCX" eventType="valueChanged" id="k8p-F7-ILf"/>
</connections>
</segmentedControl>
</subviews>
</stackView>
</subviews>
<constraints>
<constraint firstItem="nTa-ey-RFM" firstAttribute="top" secondItem="scU-nK-qiI" secondAttribute="top" constant="20" id="VT0-G5-ofZ"/>
<constraint firstAttribute="bottom" secondItem="nTa-ey-RFM" secondAttribute="bottom" constant="20" id="XR5-nT-N05"/>
<constraint firstItem="nTa-ey-RFM" firstAttribute="leading" secondItem="scU-nK-qiI" secondAttribute="leading" constant="20" id="oB6-ry-kjt"/>
<constraint firstAttribute="trailing" secondItem="nTa-ey-RFM" secondAttribute="trailing" constant="20" id="p7Q-Bn-sSi"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="labelView" destination="Sap-Qr-1ZU" id="qJ3-Wb-qrb"/>
<outlet property="segmentedControl" destination="rJq-eZ-wvm" id="gKh-IH-iQO"/>
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="MCIntegerField" id="KwC-8P-1dy" userLabel="MCIntegerField" customClass="MCIntegerFieldTableViewCell">
<rect key="frame" x="0.0" y="120" width="1194" height="95.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="KwC-8P-1dy" id="Dwe-Tg-VQ6">
<rect key="frame" x="0.0" y="0.0" width="1194" height="95.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="tqg-e0-rNO">
<rect key="frame" x="20" y="20" width="1154" height="55.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="clip" baselineAdjustment="alignBaselines" minimumScaleFactor="0.25" translatesAutoresizingMaskIntoConstraints="NO" id="7nN-Bo-Jgm">
<rect key="frame" x="0.0" y="0.0" width="1154" height="21"/>
<constraints>
<constraint firstAttribute="height" constant="21" id="x72-wj-jT3"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5Hz-MY-MYZ">
<rect key="frame" x="0.0" y="21" width="1154" height="34.5"/>
<subviews>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" textAlignment="natural" minimumFontSize="10" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="RCc-cF-BI3" userLabel="Text Field">
<rect key="frame" x="0.0" y="0.0" width="1060" height="34.5"/>
<constraints>
<constraint firstAttribute="height" constant="34" id="8Nj-FL-Ab6"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
<connections>
<action selector="textFieldValueChanged:" destination="KwC-8P-1dy" eventType="valueChanged" id="5In-Xx-LJB"/>
</connections>
</textField>
<stepper opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" maximumValue="100" translatesAutoresizingMaskIntoConstraints="NO" id="vgc-Rg-Zc8">
<rect key="frame" x="1060" y="0.0" width="94" height="34.5"/>
<constraints>
<constraint firstAttribute="height" constant="34" id="HhX-Qt-vVP"/>
</constraints>
<connections>
<action selector="stepperValueChanged:" destination="KwC-8P-1dy" eventType="valueChanged" id="BKh-dh-f3d"/>
</connections>
</stepper>
</subviews>
</stackView>
</subviews>
</stackView>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="tqg-e0-rNO" secondAttribute="trailing" constant="20" id="9U8-8r-ZOV"/>
<constraint firstItem="tqg-e0-rNO" firstAttribute="top" secondItem="Dwe-Tg-VQ6" secondAttribute="top" constant="20" id="Ctm-x1-7Uf"/>
<constraint firstAttribute="bottom" secondItem="tqg-e0-rNO" secondAttribute="bottom" constant="20" id="gHo-d9-oh1"/>
<constraint firstItem="tqg-e0-rNO" firstAttribute="leading" secondItem="Dwe-Tg-VQ6" secondAttribute="leading" constant="20" id="pSJ-yP-eoc"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="labelView" destination="7nN-Bo-Jgm" id="TC4-Xt-Rrb"/>
<outlet property="stepper" destination="vgc-Rg-Zc8" id="QT0-L8-npw"/>
<outlet property="textField" destination="RCc-cF-BI3" id="JpG-sb-BPS"/>
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="MCShortStringField" id="s66-ds-RzY" userLabel="MCShortStringField" customClass="MCShortStringFieldTableViewCell">
<rect key="frame" x="0.0" y="215.5" width="1194" height="74.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="s66-ds-RzY" id="hip-Sr-WMn">
<rect key="frame" x="0.0" y="0.0" width="1194" height="74.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" textAlignment="natural" minimumFontSize="17" clearButtonMode="whileEditing" translatesAutoresizingMaskIntoConstraints="NO" id="vSp-Bb-6Ii">
<rect key="frame" x="20" y="20" width="1154" height="34.5"/>
<constraints>
<constraint firstAttribute="height" constant="34" id="bOl-6v-0Mc"/>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="34" id="wG5-Lz-PNz"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
<connections>
<action selector="textFieldValueChanged:" destination="s66-ds-RzY" eventType="valueChanged" id="aXL-JN-6WT"/>
</connections>
</textField>
</subviews>
<constraints>
<constraint firstItem="vSp-Bb-6Ii" firstAttribute="centerY" secondItem="hip-Sr-WMn" secondAttribute="centerY" id="8vy-Pl-D15"/>
<constraint firstItem="vSp-Bb-6Ii" firstAttribute="top" secondItem="hip-Sr-WMn" secondAttribute="top" constant="20" id="Olm-z3-U8k"/>
<constraint firstItem="vSp-Bb-6Ii" firstAttribute="leading" secondItem="hip-Sr-WMn" secondAttribute="leading" constant="20" id="QZR-Jm-4WV"/>
<constraint firstAttribute="bottom" secondItem="vSp-Bb-6Ii" secondAttribute="bottom" constant="20" id="oTQ-Be-G2j"/>
<constraint firstAttribute="trailing" secondItem="vSp-Bb-6Ii" secondAttribute="trailing" constant="20" id="x9A-uQ-TpO"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="textField" destination="vSp-Bb-6Ii" id="LsH-bK-Vns"/>
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="MCBooleanField" id="aNO-fN-IMT" userLabel="MCBooleanField" customClass="MCBooleanFieldTableViewCell">
<rect key="frame" x="0.0" y="290" width="1194" height="71.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="aNO-fN-IMT" id="S88-r0-N1u">
<rect key="frame" x="0.0" y="0.0" width="1194" height="71.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="mgS-vl-2ZB">
<rect key="frame" x="20" y="20" width="1154" height="31.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="tsg-zB-HOv">
<rect key="frame" x="0.0" y="0.0" width="1085" height="31.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="L1a-6J-Yco">
<rect key="frame" x="1105" y="0.0" width="51" height="31.5"/>
<constraints>
<constraint firstAttribute="width" constant="49" id="b2p-dO-cAL"/>
</constraints>
<connections>
<action selector="valueChanged:" destination="aNO-fN-IMT" eventType="valueChanged" id="uHk-k4-oPq"/>
</connections>
</switch>
</subviews>
</stackView>
</subviews>
<constraints>
<constraint firstItem="mgS-vl-2ZB" firstAttribute="top" secondItem="S88-r0-N1u" secondAttribute="top" constant="20" id="Q94-qT-yUA"/>
<constraint firstItem="mgS-vl-2ZB" firstAttribute="leading" secondItem="S88-r0-N1u" secondAttribute="leading" constant="20" id="YCf-EG-kId"/>
<constraint firstAttribute="bottom" secondItem="mgS-vl-2ZB" secondAttribute="bottom" constant="20" id="a7T-95-BKz"/>
<constraint firstAttribute="trailing" secondItem="mgS-vl-2ZB" secondAttribute="trailing" constant="20" id="rE7-sz-lIl"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="labelView" destination="tsg-zB-HOv" id="ky7-dd-2zz"/>
<outlet property="switchView" destination="L1a-6J-Yco" id="tTF-vZ-9gZ"/>
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="MCSelectField" id="PQF-mC-UlI" userLabel="MCSelectField" customClass="MCSelectFieldTableViewCell">
<rect key="frame" x="0.0" y="361.5" width="1194" height="74.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="PQF-mC-UlI" id="Wcu-Sa-z2O">
<rect key="frame" x="0.0" y="0.0" width="1194" height="74.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="NMZ-Q6-qaW">
<rect key="frame" x="20" y="20" width="1154" height="34.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lik-sI-94j" userLabel="Label View">
<rect key="frame" x="0.0" y="0.0" width="41.5" height="34.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="DFa-Hd-laX" userLabel="Text Field">
<rect key="frame" x="61.5" y="0.0" width="1092.5" height="34.5"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
</textField>
</subviews>
</stackView>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="NMZ-Q6-qaW" secondAttribute="trailing" constant="20" id="5M4-bs-UBx"/>
<constraint firstAttribute="bottom" secondItem="NMZ-Q6-qaW" secondAttribute="bottom" constant="20" id="8UW-EN-g5N"/>
<constraint firstItem="NMZ-Q6-qaW" firstAttribute="top" secondItem="Wcu-Sa-z2O" secondAttribute="top" constant="20" id="uKb-SO-18n"/>
<constraint firstItem="NMZ-Q6-qaW" firstAttribute="leading" secondItem="Wcu-Sa-z2O" secondAttribute="leading" constant="20" id="uxf-ZS-UgY"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="labelView" destination="lik-sI-94j" id="TYU-vE-Agy"/>
<outlet property="textField" destination="DFa-Hd-laX" id="8fm-vK-dar"/>
</connections>
</tableViewCell>
</prototypes>
</tableView>
</subviews>
<viewLayoutGuide key="safeArea" id="PhC-ja-AcN"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="Ph7-w5-jbm" firstAttribute="leading" secondItem="PhC-ja-AcN" secondAttribute="leading" id="Z21-h9-5uc"/>
<constraint firstItem="PhC-ja-AcN" firstAttribute="bottom" secondItem="Ph7-w5-jbm" secondAttribute="bottom" id="sSZ-aH-KLX"/>
<constraint firstItem="Ph7-w5-jbm" firstAttribute="top" secondItem="PhC-ja-AcN" secondAttribute="top" id="utK-QF-3ZS"/>
<constraint firstItem="PhC-ja-AcN" firstAttribute="trailing" secondItem="Ph7-w5-jbm" secondAttribute="trailing" id="y6Z-Sy-84h"/>
</constraints>
</view>
<navigationItem key="navigationItem" title="Edit Monster" id="6DN-Xf-XAZ">
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="4Ld-nj-3La">
<connections>
<segue destination="IHZ-ur-HNo" kind="unwind" identifier="DiscardChanges" unwindAction="unwindWithSegue:" id="bO7-E6-0Qd"/>
</connections>
</barButtonItem>
<barButtonItem key="rightBarButtonItem" title="Save" id="fCy-Er-XgT">
<connections>
<segue destination="IHZ-ur-HNo" kind="unwind" identifier="SaveChanges" unwindAction="unwindWithSegue:" id="zmX-J1-j2M"/>
</connections>
</barButtonItem>
</navigationItem>
<connections>
<outlet property="monsterTableView" destination="Ph7-w5-jbm" id="O5X-1g-Uvp"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="cpm-3g-PFo" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
<exit id="IHZ-ur-HNo" userLabel="Exit" sceneMemberID="exit"/>
</objects>
<point key="canvasLocation" x="3347.826086956522" y="1268.9732142857142"/>
</scene>
</scenes>
<inferredMetricsTieBreakers>
<segue reference="W6E-7q-Wzb"/>
</inferredMetricsTieBreakers>
<resources>
<image name="book.fill" catalog="system" width="128" height="102"/>
<image name="magnifyingglass" catalog="system" width="128" height="115"/>
<image name="plus" catalog="system" width="128" height="113"/>
<image name="rectangle.3.offgrid.fill" catalog="system" width="128" height="81"/>
<image name="section-divider" width="800" height="20"/>
<image name="tray.full.fill" catalog="system" width="128" height="88"/>
<systemColor name="secondaryLabelColor">
<color red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

View File

@@ -0,0 +1,20 @@
//
// Color+Hex.swift
// MonsterCards
//
// Created by Tom Hicks on 1/17/21.
//
import Foundation
import SwiftUI
extension Color {
init(hex: UInt, alpha: Double = 1) {
self.init(
.sRGB,
red: Double((hex >> 16) & 0xff) / 255,
green: Double((hex >> 8) & 0xff) / 255,
blue: Double((hex >> 0) & 0xff) / 255,
opacity: alpha)
}
}

View File

@@ -1,19 +0,0 @@
//
// HtmlHelper.h
// MonsterCards
//
// Created by Tom Hicks on 9/12/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface HTMLHelper : NSObject
+(NSAttributedString*)attributedStringFromHTML:(NSString*)html;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,24 +0,0 @@
//
// HtmlHelper.m
// MonsterCards
//
// Created by Tom Hicks on 9/12/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "HTMLHelper.h"
@import UIKit;
@implementation HTMLHelper
+ (NSAttributedString*)attributedStringFromHTML:(NSString *)htmlString {
NSData *data = [htmlString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
NSDictionary *options = @{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)};
return [[NSAttributedString alloc] initWithData:data
options:options
documentAttributes:nil
error:nil];
}
@end

View File

@@ -1,51 +0,0 @@
//
// JSONHelper.h
// MonsterCards
//
// Created by Tom Hicks on 9/15/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface JSONHelper : NSObject
+(NSString*)readStringFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key;
+(NSString*)readStringFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key withDefaultValue:(NSString* _Nullable)defaultValue;
+(NSNumber*)readNumberFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key;
+(NSNumber*)readNumberFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key withDefaultValue:(NSNumber* _Nullable)defaultValue;
+(int)readIntFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key;
+(int)readIntFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key withDefaultValue:(int)defaultValue;
+(BOOL)readBoolFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key;
+(BOOL)readBoolFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key withDefaultValue:(BOOL)defaultValue;
+(NSDictionary*)readDictionaryFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key;
+(NSDictionary*)readDictionaryFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key withDefaultValue:(NSDictionary* _Nullable)defaultValue;
+(NSArray*)readArrayFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key;
+(NSArray*)readArrayFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key withDefaultValue:(NSArray* _Nullable)defaultValue;
+(NSString*)readStringFromArray:(NSArray*)array forIndex:(NSUInteger)index;
+(NSString*)readStringFromArray:(NSArray*)array forIndex:(NSUInteger)index withDefaultValue:(NSString* _Nullable)defaultValue;
+(NSNumber*)readNumberFromArray:(NSArray*)array forIndex:(NSUInteger)index;
+(NSNumber*)readNumberFromArray:(NSArray*)array forIndex:(NSUInteger)index withDefaultValue:(NSNumber* _Nullable)defaultValue;
+(int)readIntFromArray:(NSArray*)array forIndex:(NSUInteger)index;
+(int)readIntFromArray:(NSArray*)array forIndex:(NSUInteger)index withDefaultValue:(int)defaultValue;
+(BOOL)readBoolFromArray:(NSArray*)array forIndex:(NSUInteger)index;
+(BOOL)readBoolFromArray:(NSArray*)array forIndex:(NSUInteger)index withDefaultValue:(BOOL)defaultValue;
+(NSDictionary*)readDictionaryFromArray:(NSArray*)array forIndex:(NSUInteger)index;
+(NSDictionary*)readDictionaryFromArray:(NSArray*)array forIndex:(NSUInteger)index withDefaultValue:(NSDictionary* _Nullable)defaultValue;
+(NSArray*)readArrayFromArray:(NSArray*)array forIndex:(NSUInteger)index;
+(NSArray*)readArrayFromArray:(NSArray*)array forIndex:(NSUInteger)index withDefaultValue:(NSArray* _Nullable)defaultValue;
+(id)parseJSONString:(NSString*)jsonString;
+(NSDictionary*)parseJSONStringAsDictionary:(NSString*)jsonString;
+(NSArray*)parseJSONStringAsArray:(NSString*)jsonString;
+(id)parseJSONData:(NSData*)jsonData;
+(NSDictionary*)parseJSONDataAsDictionary:(NSData*)jsonData;
+(NSArray*)parseJSONDataAsArray:(NSData*)jsonData;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,227 +0,0 @@
//
// JSONHelper.m
// MonsterCards
//
// Created by Tom Hicks on 9/15/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "JSONHelper.h"
@implementation JSONHelper
NSString* coerceObjectToString(NSObject *object, NSString *defaultValue) {
if ([object isKindOfClass:[NSString class]]) {
return (NSString*)object;
}
return defaultValue;
}
NSNumber* coerceObjectToNumber(NSObject *object, NSNumber *defaultValue) {
if ([object isKindOfClass:[NSNumber class]]) {
return (NSNumber*)object;
}
return defaultValue;
}
int coerceObjectToInt(NSObject *object, int defaultValue) {
if ([object isKindOfClass:[NSNumber class]]) {
return [(NSNumber*)object intValue];
}
if ([object isKindOfClass:[NSString class]]) {
NSScanner *scanner;
int temp;
scanner = [NSScanner scannerWithString:(NSString*)object];
if ([scanner scanInt:&temp]) {
return temp;
}
}
return defaultValue;
}
BOOL coerceObjectToBool(NSObject *object, BOOL defaultValue) {
if ([object isKindOfClass:[NSNumber class]]) {
return [(NSNumber*)object boolValue];
}
return defaultValue;
}
+(NSString*)readStringFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key {
return [JSONHelper readStringFromDictionary:dictionary forKey:key withDefaultValue:nil];
}
+(NSString*)readStringFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key withDefaultValue:(NSString* _Nullable)defaultValue {
NSObject *object = [dictionary objectForKey:key];
return coerceObjectToString(object, defaultValue);
}
+(NSNumber*)readNumberFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key {
return [JSONHelper readNumberFromDictionary:dictionary forKey:key withDefaultValue:nil];
}
+(NSNumber*)readNumberFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key withDefaultValue:(NSNumber* _Nullable)defaultValue {
NSObject *object = [dictionary objectForKey:key];
return coerceObjectToNumber(object, defaultValue);
}
+(int)readIntFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key {
return [JSONHelper readIntFromDictionary:dictionary forKey:key withDefaultValue:0];
}
+(int)readIntFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key withDefaultValue:(int)defaultValue {
NSObject *object = [dictionary objectForKey:key];
return coerceObjectToInt(object, defaultValue);
}
+(BOOL)readBoolFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key {
return [JSONHelper readBoolFromDictionary:dictionary forKey:key withDefaultValue:NO];
}
+(BOOL)readBoolFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key withDefaultValue:(BOOL)defaultValue {
NSObject *object = [dictionary objectForKey:key];
return coerceObjectToBool(object, defaultValue);
}
+(NSDictionary*)readDictionaryFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key {
return [JSONHelper readDictionaryFromDictionary:dictionary forKey:key withDefaultValue:nil];
}
+(NSDictionary*)readDictionaryFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key withDefaultValue:(NSDictionary* _Nullable)defaultValue {
NSObject *object = [dictionary objectForKey:key];
if ([object isKindOfClass:[NSDictionary class]]) {
return (NSDictionary*)object;
}
return defaultValue;
}
+(NSArray*)readArrayFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key {
return [JSONHelper readArrayFromDictionary:dictionary forKey:key withDefaultValue:nil];
}
+(NSArray*)readArrayFromDictionary:(NSDictionary*)dictionary forKey:(NSString*)key withDefaultValue:(NSArray* _Nullable)defaultValue {
NSObject *object = [dictionary objectForKey:key];
if ([object isKindOfClass:[NSArray class]]) {
return (NSArray*)object;
}
return defaultValue;
}
+(NSString*)readStringFromArray:(NSArray*)array forIndex:(NSUInteger)index{
return [JSONHelper readStringFromArray:array forIndex:index withDefaultValue:nil];
}
+(NSString*)readStringFromArray:(NSArray*)array forIndex:(NSUInteger)index withDefaultValue:(NSString* _Nullable)defaultValue {
NSObject *object = [array objectAtIndex:index];
return coerceObjectToString(object, defaultValue);
}
+(NSNumber*)readNumberFromArray:(NSArray*)array forIndex:(NSUInteger)index {
return [JSONHelper readNumberFromArray:array forIndex:index withDefaultValue:nil];
}
+(NSNumber*)readNumberFromArray:(NSArray*)array forIndex:(NSUInteger)index withDefaultValue:(NSNumber* _Nullable)defaultValue {
NSObject *object = [array objectAtIndex:index];
return coerceObjectToNumber(object, defaultValue);
}
+(int)readIntFromArray:(NSArray*)array forIndex:(NSUInteger)index {
return [JSONHelper readIntFromArray:array forIndex:index withDefaultValue:0];
}
+(int)readIntFromArray:(NSArray*)array forIndex:(NSUInteger)index withDefaultValue:(int)defaultValue {
NSObject *object = [array objectAtIndex:index];
return coerceObjectToInt(object, defaultValue);
}
+(BOOL)readBoolFromArray:(NSArray*)array forIndex:(NSUInteger)index {
return [JSONHelper readBoolFromArray:array forIndex:index withDefaultValue:nil];
}
+(BOOL)readBoolFromArray:(NSArray*)array forIndex:(NSUInteger)index withDefaultValue:(BOOL)defaultValue {
NSObject *object = [array objectAtIndex:index];
return coerceObjectToBool(object, defaultValue);
}
+(NSDictionary*)readDictionaryFromArray:(NSArray*)array forIndex:(NSUInteger)index {
return [JSONHelper readDictionaryFromArray:array forIndex:index withDefaultValue:nil];
}
+(NSDictionary*)readDictionaryFromArray:(NSArray*)array forIndex:(NSUInteger)index withDefaultValue:(NSDictionary* _Nullable)defaultValue {
NSObject *object = [array objectAtIndex:index];
if ([object isKindOfClass:[NSDictionary class]]) {
return (NSDictionary*)object;
}
return defaultValue;
}
+(NSArray*)readArrayFromArray:(NSArray*)array forIndex:(NSUInteger)index {
return [JSONHelper readArrayFromArray:array forIndex:index withDefaultValue:nil];
}
+(NSArray*)readArrayFromArray:(NSArray*)array forIndex:(NSUInteger)index withDefaultValue:(NSArray* _Nullable)defaultValue {
NSObject *object = [array objectAtIndex:index];
if ([object isKindOfClass:[NSArray class]]) {
return (NSArray*)object;
}
return defaultValue;
}
+(id)parseJSONString:(NSString*)jsonString {
NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSArray *jsonRoot = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil];
return jsonRoot;
}
+(NSDictionary*)parseJSONStringAsDictionary:(NSString*)jsonString {
NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *jsonRoot = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil];
if (![jsonRoot isKindOfClass:[NSDictionary class]]) {
return nil;
} else {
return jsonRoot;
}
}
+(NSArray*)parseJSONStringAsArray:(NSString*)jsonString {
NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSArray *jsonRoot = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil];
if (![jsonRoot isKindOfClass:[NSArray class]]) {
return nil;
} else {
return jsonRoot;
}
}
+(id)parseJSONData:(NSData*)jsonData {
NSArray *jsonRoot = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil];
return jsonRoot;
}
+(NSDictionary*)parseJSONDataAsDictionary:(NSData*)jsonData {
NSDictionary *jsonRoot = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil];
if (![jsonRoot isKindOfClass:[NSDictionary class]]) {
return nil;
} else {
return jsonRoot;
}
}
+(NSArray*)parseJSONDataAsArray:(NSData*)jsonData {
NSArray *jsonRoot = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil];
if (![jsonRoot isKindOfClass:[NSArray class]]) {
return nil;
} else {
return jsonRoot;
}
}
@end

View File

@@ -1,19 +0,0 @@
//
// StringHelper.h
// MonsterCards
//
// Created by Tom Hicks on 9/5/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface StringHelper : NSObject
+(BOOL)isStringNilOrEmpty:(NSString*)theString;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,25 +0,0 @@
//
// StringHelper.m
// MonsterCards
//
// Created by Tom Hicks on 9/5/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "StringHelper.h"
@implementation StringHelper
+(BOOL)isStringNilOrEmpty:(NSString*)theString {
if (nil == theString) {
return YES;
}
if (theString.length < 1) {
return YES;
}
return NO;
}
@end

View File

@@ -25,40 +25,16 @@
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
<true/>
</dict>
</array>
</dict>
</dict>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchScreen</key>
<dict/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UIStatusBarTintParameters</key>
<dict>
<key>UINavigationBar</key>
<dict>
<key>Style</key>
<string>UIBarStyleDefault</string>
<key>Translucent</key>
<false/>
</dict>
</dict>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>

View File

@@ -1,22 +0,0 @@
//
// Ability.h
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Ability : NSObject
@property NSString* name;
@property NSString* abilityDescription;
-(id)initWithName: (NSString*)name andDescription: (NSString*)description;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,31 +0,0 @@
//
// Ability.m
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "Ability.h"
@implementation Ability
-(id)init {
self = [super init];
self.name = @"";
self.abilityDescription = @"";
return self;
}
-(id)initWithName: (NSString*)name andDescription: (NSString*)description {
self = [super init];
self.name = name;
self.abilityDescription = description;
return self;
}
@end

View File

@@ -1,22 +0,0 @@
//
// Action.h
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Action : NSObject
@property NSString* name;
@property NSString* actionDescription;
-(id)initWithName: (NSString*)name andDescription: (NSString*)description;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,31 +0,0 @@
//
// Action.m
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "Action.h"
@implementation Action
-(id)init {
self = [super init];
self.name = @"";
self.actionDescription = @"";
return self;
}
-(id)initWithName: (NSString*)name andDescription: (NSString*)description {
self = [super init];
self.name = name;
self.actionDescription = description;
return self;
}
@end

View File

@@ -0,0 +1,27 @@
//
// AdvantageType.swift
// MonsterCards
//
// Created by Tom Hicks on 1/17/21.
//
import Foundation
enum AdvantageType: String, CaseIterable, Identifiable {
case none = "none"
case advantage = "advantage"
case disadvantage = "disadvantage"
var id: AdvantageType { self }
var displayName: String {
switch self {
case .none:
return "None"
case .advantage:
return "Advantage"
case .disadvantage:
return "Disadvantage"
}
}
}

View File

@@ -1,23 +0,0 @@
//
// DamageType.h
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface DamageType : NSObject
@property NSString* name;
@property NSString* note;
@property NSString* type;
-(id)initWithName: (NSString*)name note: (NSString*)note andType: (NSString*)type;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,33 +0,0 @@
//
// DamageType.m
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "DamageType.h"
@implementation DamageType
-(id)init {
self = [super init];
self.name = @"";
self.note = @"";
self.type = @"";
return self;
}
-(id)initWithName: (NSString*)name note: (NSString*)note andType: (NSString*)type{
self = [super init];
self.name = name;
self.note = note;
self.type = type;
return self;
}
@end

View File

@@ -1,23 +0,0 @@
//
// Language.h
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Language : NSObject
@property NSString* name;
@property BOOL speaks;
-(id)initWithName:(NSString*)name
andSpeaks:(BOOL)canSpeak;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,32 +0,0 @@
//
// Language.m
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "Language.h"
@implementation Language
-(id)init {
self = [super init];
self.name = @"";
self.speaks = YES;
return self;
}
-(id)initWithName:(NSString*)name
andSpeaks:(BOOL)canSpeak {
self = [super init];
self.name = name;
self.speaks = canSpeak;
return self;
}
@end

View File

@@ -0,0 +1,701 @@
//
// Monster+CoreDataClass.swift
// MonsterCards
//
// Created by Tom Hicks on 1/15/21.
//
//
import Foundation
import CoreData
@objc(Monster)
public class Monster: NSManagedObject {
convenience init(context: NSManagedObjectContext, name: String, size: String, type: String, subtype: String, alignment: String) {
self.init(context:context)
self.name = name;
self.size = size;
self.type = type;
self.subtype = subtype;
self.alignment = alignment;
}
// hasCustomProficiencyBonus
// telepathy int in json but seems like it should be a bool
let kBaseArmorClassUnarmored = 10;
let kBaseArmorClassMageArmor = 13;
let kBaseArmorClassPadded = 11;
let kBaseArmorClassLeather = 11;
let kBaseArmorClassStudded = 12;
let kBaseArmorClassHide = 12;
let kBaseArmorClassChainShirt = 13;
let kBaseArmorClassScaleMail = 14;
let kBaseArmorClassBreastplate = 14;
let kBaseArmorClassHalfPlate = 15;
let kBaseArmorClassRingMail = 14;
let kBaseArmorClassChainMail = 16;
let kBaseArmorClassSplintMail = 17;
let kBaseArmorClassPlate = 18;
// MARK: Basic Info
var meta: String {
get {
// size type (subtype) alignment
var parts: [String] = []
if (!(self.size?.isEmpty ?? false)) {
parts.append(self.size!)
}
if (!(self.type?.isEmpty ?? false)) {
parts.append(self.type!)
}
if (!(self.subtype?.isEmpty ?? false)) {
parts.append(String.init(format: "(%@)", arguments: [self.subtype!]))
}
if (!(self.alignment?.isEmpty ?? false)) {
parts.append(self.alignment!)
}
return parts.joined(separator: " ")
}
}
var sizeEnum: SizeType {
get {
return SizeType.init(rawValue: size ?? "") ?? .medium
}
set {
size = newValue.rawValue
}
}
var hitPoints: String {
get {
if (hasCustomHP) {
return customHP ?? "";
} else {
let dieSize = Double(Monster.hitDieForSize(sizeEnum))
let conMod = Double(constitutionModifier)
let level1HP = Double(dieSize + conMod)
let extraLevels = Double(hitDice - 1)
let levelNHP = (dieSize + 1.0) / 2.0 + conMod
let extraLevelsHP = extraLevels * levelNHP
let hpTotal = Int(ceil(level1HP + extraLevelsHP))
// let hpTotal = Int(max(1, ceil(dieSize + conMod + (hitDice - 1) * ((dieSize + 1) / 2.0 + conMod))))
let conBonus = Int(conMod) * Int(hitDice)
return String(format: "%d (%dd%d%+d)", hpTotal, hitDice, dieSize, conBonus)
}
}
}
var speed: String {
get {
if (hasCustomSpeed) {
return customSpeed ?? ""
} else {
var parts: [String] = []
if (baseSpeed > 0) {
parts.append("\(baseSpeed) ft.")
}
if (burrowSpeed > 0) {
parts.append("burrow \(burrowSpeed) ft.")
}
if (climbSpeed > 0) {
parts.append("climb \(climbSpeed) ft.")
}
if (flySpeed > 0) {
parts.append("fly \(flySpeed) ft.\(canHover ? " (hover)": "")")
}
if (swimSpeed > 0) {
parts.append("swim \(swimSpeed) ft.")
}
return parts.joined(separator: ", ")
}
}
}
class func hitDieForSize(_ size: SizeType) -> Int {
switch size {
case .tiny: return 4
case .small: return 6
case .medium: return 8
case .large: return 10
case .huge: return 12
case .gargantuan: return 20
}
}
// MARK: Ability Scores
class func abilityModifierForScore(_ score: Int) -> Int {
return Int(floor(Double((score - 10)) / 2.0))
}
var strengthModifier: Int {
get {
return Monster.abilityModifierForScore(Int(strengthScore))
}
}
var dexterityModifier: Int {
get {
return Monster.abilityModifierForScore(Int(dexterityScore))
}
}
var constitutionModifier: Int {
get {
return Monster.abilityModifierForScore(Int(constitutionScore))
}
}
var intelligenceModifier: Int {
get {
return Monster.abilityModifierForScore(Int(intelligenceScore))
}
}
var wisdomModifier: Int {
get {
return Monster.abilityModifierForScore(Int(wisdomScore))
}
}
var charismaModifier: Int {
get {
return Monster.abilityModifierForScore(Int(charismaScore))
}
}
var strengthDescription: String {
get {
return String(format: "%d (%+d)", strengthScore, strengthModifier)
}
}
var dexterityDescription: String {
get {
return String(format: "%d (%+d)", dexterityScore, dexterityModifier)
}
}
var constitutionDescription: String {
get {
return String(format: "%d (%+d)", constitutionScore, constitutionModifier)
}
}
var intelligenceDescription: String {
get {
return String(format: "%d (%+d)", intelligenceScore, intelligenceModifier)
}
}
var wisdomDescription: String {
get {
return String(format: "%d (%+d)", wisdomScore, wisdomModifier)
}
}
var charismaDescription: String {
get {
return String(format: "%d (%+d)", charismaScore, charismaModifier)
}
}
// MARK: Armor
var armorTypeEnum: ArmorType {
get {
return ArmorType.init(rawValue: armorType ?? "none") ?? .none
}
set {
armorType = newValue.rawValue
}
}
var armorClassDescription: String {
get {
let hasShield = shieldBonus != 0
var armorClassTotal = 0
if (armorTypeEnum == ArmorType.none) {
// 10 + dexMod + 2 for shieldBonus "15" or "17 (shield)"
armorClassTotal = kBaseArmorClassUnarmored + dexterityModifier + Int(shieldBonus)
return "\(armorClassTotal)\(hasShield ? " (shield)" : "")"
} else if (armorTypeEnum == .naturalArmor) {
// 10 + dexMod + naturalArmorBonus + 2 for shieldBonus "16 (natural armor)" or "18 (natural armor, shield)"
armorClassTotal = kBaseArmorClassUnarmored + dexterityModifier + Int(naturalArmorBonus) + Int(shieldBonus)
return "\(armorClassTotal) (natural armor\(hasShield ? " (shield)" : ""))"
} else if (armorTypeEnum == .mageArmor) {
// 10 + dexMod + 2 for shield + 3 for mage armor "15 (18 with mage armor)" or 17 (shield, 20 with mage armor)
armorClassTotal = kBaseArmorClassUnarmored + dexterityModifier + Int(shieldBonus)
let acWithMageArmor = kBaseArmorClassMageArmor + dexterityModifier + Int(shieldBonus)
return String(format: "%d (%@%d with mage armor)", armorClassTotal, (hasShield ? "shield, " : ""), acWithMageArmor)
} else if (armorTypeEnum == .padded) {
// 11 + dexMod + 2 for shield "18 (padded armor, shield)"
armorClassTotal = kBaseArmorClassPadded + dexterityModifier + Int(shieldBonus)
return String(format: "%d (padded%@)", armorClassTotal, (hasShield ? "shield, " : ""))
} else if (armorTypeEnum == .leather) {
// 11 + dexMod + 2 for shield "18 (leather, shield)"
armorClassTotal = kBaseArmorClassLeather + dexterityModifier + Int(shieldBonus)
return String(format:"%d (leather%@)", armorClassTotal, (hasShield ? "shield, " : ""))
} else if (armorTypeEnum == .studdedLeather) {
// 12 + dexMod +2 for shield "17 (studded leather)"
armorClassTotal = kBaseArmorClassStudded + dexterityModifier + Int(shieldBonus)
return String(format: "%d (studded leather%@)", armorClassTotal, (hasShield ? "shield, " : ""))
} else if (armorTypeEnum == .hide) {
// 12 + Min(2, dexMod) + 2 for shield "12 (hide armor)"
armorClassTotal = kBaseArmorClassHide + min(2, dexterityModifier) + Int(shieldBonus)
return String(format: "%d (hide%@)", armorClassTotal, (hasShield ? ", shield" : ""))
} else if (armorTypeEnum == .chainShirt) {
// 13 + Min(2, dexMod) + 2 for shield "12 (chain shirt)"
armorClassTotal = kBaseArmorClassChainShirt + min(2, dexterityModifier) + Int(shieldBonus)
return String(format: "%d (chain shirt%@)", armorClassTotal, (hasShield ? ", shield" : ""))
} else if (armorTypeEnum == .scaleMail) {
// 14 + Min(2, dexMod) + 2 for shield "14 (scale mail)"
armorClassTotal = kBaseArmorClassScaleMail + min(2, dexterityModifier) + Int(shieldBonus)
return String(format: "%d (scale mail%@)", armorClassTotal, (hasShield ? ", shield" : ""))
} else if (armorTypeEnum == .breastplate) {
// 14 + Min(2, dexMod) + 2 for shield "16 (breastplate)"
armorClassTotal = kBaseArmorClassBreastplate + min(2, dexterityModifier) + Int(shieldBonus)
return String(format: "%d (breastplate%@)", armorClassTotal, (hasShield ? ", shield" : ""))
} else if (armorTypeEnum == .halfPlate) {
// 15 + Min(2, dexMod) + 2 for shield "17 (half plate)"
armorClassTotal = kBaseArmorClassHalfPlate + min(2, dexterityModifier) + Int(shieldBonus)
return String(format: "%d (half plate%@)", armorClassTotal, (hasShield ? ", shield" : ""))
} else if (armorTypeEnum == .ringMail) {
// 14 + 2 for shield "14 (ring mail)
armorClassTotal = kBaseArmorClassRingMail + Int(shieldBonus)
return String(format: "%d (ring mail%@)", armorClassTotal, (hasShield ? ", shield" : ""))
} else if (armorTypeEnum == .chainMail) {
// 16 + 2 for shield "16 (chain mail)"
armorClassTotal = kBaseArmorClassChainMail + Int(shieldBonus)
return String(format: "%d (chain mail%@)", armorClassTotal, (hasShield ? ", shield" : ""))
} else if (armorTypeEnum == .splintMail) {
// 17 + 2 for shield "17 (splint)"
armorClassTotal = kBaseArmorClassSplintMail + Int(shieldBonus)
return String(format: "%d (splint%@)", armorClassTotal, (hasShield ? ", shield" : ""))
} else if (armorTypeEnum == .plateMail) {
// 18 + 2 for shield "18 (plate)"
armorClassTotal = kBaseArmorClassPlate + Int(shieldBonus)
return String(format: "%d (plate%@)", armorClassTotal, (hasShield ? ", shield" : ""))
} else if (armorTypeEnum == .other) {
// pure string value shield check does nothing just copies the string from otherArmorDesc
return otherArmorDescription ?? "";
} else {
return ""
}
}
}
// MARK: Challenge Rating / Proficiency Bonus
var challengeRatingEnum: ChallengeRating {
get {
return ChallengeRating.init(rawValue: challengeRating ?? "1") ?? .one
}
set {
challengeRating = newValue.rawValue
}
}
var proficiencyBonus: Int {
return 5
// switch challengeRatingEnum {
// case .custom:
// return Int(customProficiencyBonus)
// case .zero:
// fallthrough
// case .oneEighth:
// fallthrough
// case .oneQuarter:
// fallthrough
// case .oneHalf:
// fallthrough
// case .one:
// fallthrough
// case .two:
// fallthrough
// case .three:
// fallthrough
// case .four:
// return 2
// case .five:
// fallthrough
// case .six:
// fallthrough
// case .seven:
// fallthrough
// case .eight:
// return 3
// case .nine:
// fallthrough
// case .ten:
// fallthrough
// case .eleven:
// fallthrough
// case .twelve:
// return 4
// case .thirteen:
// fallthrough
// case .fourteen:
// fallthrough
// case .fifteen:
// fallthrough
// case .sixteen:
// return 5
// case .seventeen:
// fallthrough
// case .eighteen:
// fallthrough
// case .nineteen:
// fallthrough
// case .twenty:
// return 6
// case .twentyOne:
// fallthrough
// case .twentyTwo:
// fallthrough
// case .twentyThree:
// fallthrough
// case .twentyFour:
// return 7
// case .twentyFive:
// fallthrough
// case .twentySix:
// fallthrough
// case .twentySeven:
// fallthrough
// case .twentyEight:
// return 8
// case .twentyNine:
// fallthrough
// case .thirty:
// return 9
// }
}
func proficiencyBonusForType(_ profType: ProficiencyType) -> Int {
switch profType {
case .none:
return 0
case .proficient:
return proficiencyBonus
case .expertise:
return proficiencyBonus * 2
}
}
// MARK: Saving Throws
var savingThrowsDescription: String {
get {
// TODO: port from objective-c
var parts: [String] = []
var name: String
var advantage: String
var bonus: Int
if (strengthSavingThrowAdvantageEnum != .none || strengthSavingThrowProficiencyEnum != .none) {
name = "Strength"
bonus = strengthModifier + proficiencyBonusForType(strengthSavingThrowProficiencyEnum)
advantage = Monster.advantageLabelStringForType(strengthSavingThrowAdvantageEnum)
if (!advantage.isEmpty) {
advantage = " " + advantage
}
parts.append(String(format: "%@ %+d%@", name, bonus, advantage))
}
if (dexteritySavingThrowAdvantageEnum != .none || dexteritySavingThrowProficiencyEnum != .none) {
name = "Dexterity"
bonus = dexterityModifier + proficiencyBonusForType(dexteritySavingThrowProficiencyEnum)
advantage = Monster.advantageLabelStringForType(dexteritySavingThrowAdvantageEnum)
if (!advantage.isEmpty) {
advantage = " " + advantage
}
parts.append(String(format: "%@ %+d%@", name, bonus, advantage))
}
if (constitutionSavingThrowAdvantageEnum != .none || constitutionSavingThrowProficiencyEnum != .none) {
name = "Constitution"
bonus = constitutionModifier + proficiencyBonusForType(constitutionSavingThrowProficiencyEnum)
advantage = Monster.advantageLabelStringForType(constitutionSavingThrowAdvantageEnum)
if (!advantage.isEmpty) {
advantage = " " + advantage
}
parts.append(String(format: "%@ %+d%@", name, bonus, advantage))
}
if (intelligenceSavingThrowAdvantageEnum != .none || intelligenceSavingThrowProficiencyEnum != .none) {
name = "Intelligence"
bonus = intelligenceModifier + proficiencyBonusForType(intelligenceSavingThrowProficiencyEnum)
advantage = Monster.advantageLabelStringForType(intelligenceSavingThrowAdvantageEnum)
if (!advantage.isEmpty) {
advantage = " " + advantage
}
parts.append(String(format: "%@ %+d%@", name, bonus, advantage))
}
if (wisdomSavingThrowAdvantageEnum != .none || wisdomSavingThrowProficiencyEnum != .none) {
name = "Wisdom"
bonus = wisdomModifier + proficiencyBonusForType(wisdomSavingThrowProficiencyEnum)
advantage = Monster.advantageLabelStringForType(wisdomSavingThrowAdvantageEnum)
if (!advantage.isEmpty) {
advantage = " " + advantage
}
parts.append(String(format: "%@ %+d%@", name, bonus, advantage))
}
if (charismaSavingThrowAdvantageEnum != .none || charismaSavingThrowProficiencyEnum != .none) {
name = "Charisma"
bonus = charismaModifier + proficiencyBonusForType(charismaSavingThrowProficiencyEnum)
advantage = Monster.advantageLabelStringForType(charismaSavingThrowAdvantageEnum)
if (!advantage.isEmpty) {
advantage = " " + advantage
}
parts.append(String(format: "%@ %+d%@", name, bonus, advantage))
}
return parts.joined(separator: ", ")
}
}
var strengthSavingThrowProficiencyEnum: ProficiencyType {
get {
return ProficiencyType.init(rawValue: strengthSavingThrowProficiency ?? "") ?? .none
}
set {
strengthSavingThrowProficiency = newValue.rawValue
}
}
var strengthSavingThrowAdvantageEnum: AdvantageType {
get {
return AdvantageType.init(rawValue: strengthSavingThrowAdvantage ?? "") ?? .none
}
set {
strengthSavingThrowAdvantage = newValue.rawValue
}
}
var dexteritySavingThrowProficiencyEnum: ProficiencyType {
get {
return ProficiencyType.init(rawValue: dexteritySavingThrowProficiency ?? "") ?? .none
}
set {
dexteritySavingThrowProficiency = newValue.rawValue
}
}
var dexteritySavingThrowAdvantageEnum: AdvantageType {
get {
return AdvantageType.init(rawValue: dexteritySavingThrowAdvantage ?? "") ?? .none
}
set {
dexteritySavingThrowAdvantage = newValue.rawValue
}
}
var constitutionSavingThrowProficiencyEnum: ProficiencyType {
get {
return ProficiencyType.init(rawValue: constitutionSavingThrowProficiency ?? "") ?? .none
}
set {
constitutionSavingThrowProficiency = newValue.rawValue
}
}
var constitutionSavingThrowAdvantageEnum: AdvantageType {
get {
return AdvantageType.init(rawValue: constitutionSavingThrowAdvantage ?? "") ?? .none
}
set {
constitutionSavingThrowAdvantage = newValue.rawValue
}
}
var intelligenceSavingThrowProficiencyEnum: ProficiencyType {
get {
return ProficiencyType.init(rawValue: intelligenceSavingThrowProficiency ?? "") ?? .none
}
set {
intelligenceSavingThrowProficiency = newValue.rawValue
}
}
var intelligenceSavingThrowAdvantageEnum: AdvantageType {
get {
return AdvantageType.init(rawValue: intelligenceSavingThrowAdvantage ?? "") ?? .none
}
set {
intelligenceSavingThrowAdvantage = newValue.rawValue
}
}
var wisdomSavingThrowProficiencyEnum: ProficiencyType {
get {
return ProficiencyType.init(rawValue: wisdomSavingThrowProficiency ?? "") ?? .none
}
set {
wisdomSavingThrowProficiency = newValue.rawValue
}
}
var wisdomSavingThrowAdvantageEnum: AdvantageType {
get {
return AdvantageType.init(rawValue: wisdomSavingThrowAdvantage ?? "") ?? .none
}
set {
wisdomSavingThrowAdvantage = newValue.rawValue
}
}
var charismaSavingThrowProficiencyEnum: ProficiencyType {
get {
return ProficiencyType.init(rawValue: charismaSavingThrowProficiency ?? "") ?? .none
}
set {
charismaSavingThrowProficiency = newValue.rawValue
}
}
var charismaSavingThrowAdvantageEnum: AdvantageType {
get {
return AdvantageType.init(rawValue: charismaSavingThrowAdvantage ?? "") ?? .none
}
set {
charismaSavingThrowAdvantage = newValue.rawValue
}
}
// MARK: Misc Helpers
class func advantageLabelStringForType(_ advType: AdvantageType) -> String {
switch advType {
case .none:
return ""
case .advantage:
return "(A)"
case .disadvantage:
return "(D)"
}
}
// MARK: End
}
enum ArmorType: String, CaseIterable, Identifiable {
case none = "none"
case naturalArmor = "natural armor"
case mageArmor = "mage armor"
case padded = "padded"
case leather = "leather"
case studdedLeather = "studded"
case hide = "hide"
case chainShirt = "chain shirt"
case scaleMail = "scale mail"
case breastplate = "breastplate"
case halfPlate = "half plate"
case ringMail = "ring mail"
case chainMail = "chain mail"
case splintMail = "splint"
case plateMail = "plate"
case other = "other"
var id: ArmorType { self }
var displayName: String {
switch self {
case .none: return "None"
case .naturalArmor: return "Natural Armor"
case .mageArmor: return "Mage Armor"
case .padded: return "Padded"
case .leather: return "Leather"
case .studdedLeather: return "Studded Leather"
case .hide: return "Hide"
case .chainShirt: return "Chain Shirt"
case .scaleMail: return "Scale Mail"
case .breastplate: return "Breastplate"
case .halfPlate: return "Half Plate"
case .ringMail: return "Ring Mail"
case .chainMail: return "Chain Mail"
case .splintMail: return "Splint Mail"
case .plateMail: return "Plate Mail"
case .other: return "Other"
}
}
}
enum SizeType: String, CaseIterable, Identifiable {
case tiny = "tiny"
case small = "small"
case medium = "medium"
case large = "large"
case huge = "huge"
case gargantuan = "gargantuan"
var id: SizeType { self }
var displayName: String {
switch self {
case .tiny: return "Tiny"
case .small: return "Small"
case .medium: return "Medium"
case .large: return "Large"
case .huge: return "Huge"
case .gargantuan: return "gargantuan"
}
}
}
enum ChallengeRating: String, CaseIterable, Identifiable {
case zero = "0"
case oneEighth = "1/8"
case oneQuarter = "1/4"
case oneHalf = "1/2"
case one = "1"
case two = "2"
case three = "3"
case four = "4"
case five = "5"
case six = "6"
case seven = "7"
case eight = "8"
case nine = "9"
case ten = "10"
case eleven = "11"
case twelve = "12"
case thirteen = "13"
case fourteen = "14"
case fifteen = "15"
case sixteen = "16"
case seventeen = "17"
case eighteen = "18"
case nineteen = "19"
case twenty = "20"
case twentyOne = "21"
case twentyTwo = "22"
case twentyThree = "23"
case twentyFour = "24"
case twentyFive = "25"
case twentySix = "26"
case twentySeven = "27"
case twentyEight = "28"
case twentyNine = "29"
case thirty = "30"
case custom = "*"
var id: ChallengeRating { self }
// Probably don't need this
var displayName: String {
return rawValue
}
}

View File

@@ -1,160 +0,0 @@
//
// Monster.h
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "Ability.h"
#import "Action.h"
#import "DamageType.h"
#import "Language.h"
#import "SavingThrow.h"
#import "Skill.h"
NS_ASSUME_NONNULL_BEGIN
extern NSString* const kMonsterSizeTiny;
extern NSString* const kMonsterSizeSmall;
extern NSString* const kMonsterSizeMedium;
extern NSString* const kMonsterSizeLarge;
extern NSString* const kMonsterSizeHuge;
extern NSString* const kMonsterSizeGargantuan;
extern const int kArmorClassUnarmored;
extern const int kArmorClassMageArmor;
extern const int kArmorClassPadded;
extern const int kArmorClassLeather;
extern const int kArmorClassStudded;
extern const int kArmorClassHide;
extern const int kArmorClassChainShirt;
extern const int kArmorClassScaleMail;
extern const int kArmorClassBreastplate;
extern const int kArmorClassHalfPlate;
extern const int kArmorClassRingMail;
extern const int kArmorClassChainMail;
extern const int kArmorClassSplintMail;
extern const int kArmorClassPlate;
extern NSString* const kArmorNameNone;
extern NSString* const kArmorNameNaturalArmor;
extern NSString* const kArmorNameMageArmor;
extern NSString* const kArmorNamePadded;
extern NSString* const kArmorNameLeather;
extern NSString* const kArmorNameStuddedLeather;
extern NSString* const kArmorNameHide;
extern NSString* const kArmorNameChainShirt;
extern NSString* const kArmorNameScaleMail;
extern NSString* const kArmorNameBreastplate;
extern NSString* const kArmorNameHalfPlate;
extern NSString* const kArmorNameRingMail;
extern NSString* const kArmorNameChainMail;
extern NSString* const kArmorNameSplintMail;
extern NSString* const kArmorNamePlateMail;
extern NSString* const kArmorNameOther;
extern NSString *const kProficiencyTypeNone;
extern NSString *const kProficiencyTypeProficient;
extern NSString *const kProficiencyTypeExpertise;
extern NSString *const kAdvantageTypeNone;
extern NSString *const kAdvantageTypeAdvantage;
extern NSString *const kAdvantageTypeDisadvantage;
@class Skill;
@interface Monster : NSManagedObject
// speedDescription
@property NSString *challengeRating;
@property NSString *customChallengeRating;
@property NSString *blindsightDistance;
@property NSString *darkvisionDistance;
@property NSString *tremorsenseDistance;
@property NSString *truesightDistance;
@property NSString *understandsBut;
@property int naturalArmorBonus;
@property int customProficiencyBonus;
// Shouldn't this be a BOOL?
@property int telepathy;
@property BOOL isBlind;
+(int)abilityModifierForScore: (int)score;
+(int)hitDieForSize: (NSString*)size;
-(id)initWithJSONString:(NSString*)jsonString andContext:(NSManagedObjectContext*)context;
-(id)initWithJSONData:(NSData*)jsonData andContext:(NSManagedObjectContext*)context;
-(id)initWithMonster:(Monster*)monster;
-(void)copyFromMonster:(Monster*)monster;
-(NSString*)meta;
-(int)abilityScoreForAbilityScoreName: (NSString*)abilityScoreName;
-(int)abilityModifierForAbilityScoreName: (NSString*)abilityScoreName;
-(int)strengthModifier;
-(int)dexterityModifier;
-(int)constitutionModifier;
-(int)intelligenceModifier;
-(int)wisdomModifier;
-(int)charismaModifier;
//getArmorClass
-(NSString*)armorClassDescription;
//getHitPoints
-(NSString*)hitPointsDescription;
//getSpeedText
-(NSString*)speedDescription;
-(NSString*)strengthDescription;
-(NSString*)dexterityDescription;
-(NSString*)constitutionDescription;
-(NSString*)intelligenceDescription;
-(NSString*)wisdomDescription;
-(NSString*)charismaDescription;
-(NSSet*)savingThrows;
-(void)addSavingThrow: (SavingThrow*)savingThrow;
-(void)remvoeSavingThrow: (SavingThrow*)savingThrow;
-(void)clearSavingThrows;
-(NSString*)savingThrowsDescription;
-(int)proficiencyBonus;
-(int)proficiencyBonusForType:(NSString*)proficiencyType;
-(void)addSkill: (Skill*)skill;
-(void)removeSkill: (Skill*)skill;
-(void)clearSkills;
-(NSString*)skillsDescription;
-(void)addDamageType: (DamageType*)damageType;
-(void)removeDamageType: (DamageType*)damageType;
-(void)clearDamageTypes;
-(NSString*)damageImmunitiesDescription;
-(NSString*)damageResistancesDescription;
-(NSString*)damageVulnerabilitiesDescription;
-(void)addConditionImmunity: (NSString*)condition;
-(void)removeConditionImmunity: (NSString*)condition;
-(void)clearConditionImmunities;
-(NSString*)conditionImmunitiesDescription;
-(NSString*)sensesDescription;
-(void)addLanguage: (Language*)language;
-(void)removeLanguage: (Language*)language;
-(void)clearLanguages;
-(NSString*)languagesDescription;
-(NSString*)challengeRatingDescription;
-(void)addAbility: (Ability*)ability;
-(void)removeAbility: (Ability*)ability;
-(void)clearAbilities;
-(NSArray*)abilityDescriptions;
-(void)addAction: (Action*)action;
-(void)removeAction: (Action*)action;
-(void)clearActions;
-(NSArray*)actionDescriptions;
-(NSString*)placeholderReplacedText: (NSString*)text;
-(int)savingThrowForAbilityScoreName: (NSString*)abilityScoreName;
-(int)spellSaveDCForAbilityScoreName: (NSString*)abilityScoreName;
-(int)attackBonusForAbilityScoreName: (NSString*)abilityScoreName;
@end
NS_ASSUME_NONNULL_END
#import "Monster+CoreDataProperties.h"

View File

@@ -1,699 +0,0 @@
//
// Monster.m
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "Monster.h"
#import "StringHelper.h"
#import "JSONHelper.h"
@implementation Monster
@synthesize blindsightDistance;
// TODO: move this to Core Data and add to editor
@synthesize challengeRating;
// TODO: move this to Core Data and add to editor
@synthesize customChallengeRating;
// TODO: move this to Core Data and add to editor
@synthesize customProficiencyBonus;
@synthesize darkvisionDistance;
@synthesize isBlind;
@synthesize naturalArmorBonus;
@synthesize telepathy;
@synthesize tremorsenseDistance;
@synthesize truesightDistance;
@synthesize understandsBut;
const int kArmorClassUnarmored = 10;
const int kArmorClassMageArmor = kArmorClassUnarmored + 3;
const int kArmorClassPadded = kArmorClassUnarmored + 1;
const int kArmorClassLeather = kArmorClassUnarmored + 1;
const int kArmorClassStudded = kArmorClassUnarmored + 2;
const int kArmorClassHide = kArmorClassUnarmored + 2;
const int kArmorClassChainShirt = kArmorClassUnarmored + 3;
const int kArmorClassScaleMail = kArmorClassUnarmored + 4;
const int kArmorClassBreastplate = kArmorClassUnarmored + 4;
const int kArmorClassHalfPlate = kArmorClassUnarmored + 5;
const int kArmorClassRingMail = kArmorClassUnarmored + 4;
const int kArmorClassChainMail = kArmorClassUnarmored + 6;
const int kArmorClassSplintMail = kArmorClassUnarmored + 7;
const int kArmorClassPlate = kArmorClassUnarmored + 8;
NSString* const kArmorNameNone = @"none";
NSString* const kArmorNameNaturalArmor = @"natural armor";
NSString* const kArmorNameMageArmor = @"mage armor";
NSString* const kArmorNamePadded = @"padded";
NSString* const kArmorNameLeather = @"leather";
NSString* const kArmorNameStuddedLeather = @"studded";
NSString* const kArmorNameHide = @"hide";
NSString* const kArmorNameChainShirt = @"chain shirt";
NSString* const kArmorNameScaleMail = @"scale mail";
NSString* const kArmorNameBreastplate = @"breastplate";
NSString* const kArmorNameHalfPlate = @"half plate";
NSString* const kArmorNameRingMail = @"ring mail";
NSString* const kArmorNameChainMail = @"chain mail";
NSString* const kArmorNameSplintMail = @"splint";
NSString* const kArmorNamePlateMail = @"plate";
NSString* const kArmorNameOther = @"other";
NSString* const kMonsterSizeTiny = @"tiny";
NSString* const kMonsterSizeSmall = @"small";
NSString* const kMonsterSizeMedium = @"medium";
NSString* const kMonsterSizeLarge = @"large";
NSString* const kMonsterSizeHuge = @"huge";
NSString* const kMonsterSizeGargantuan = @"gargantuan";
NSString *const kProficiencyTypeNone = @"none";
NSString *const kProficiencyTypeProficient = @"proficient";
NSString *const kProficiencyTypeExpertise = @"expertise";
NSString *const kAdvantageTypeNone = @"none";
NSString *const kAdvantageTypeAdvantage = @"advantage";
NSString *const kAdvantageTypeDisadvantage = @"disadvantage";
+(int)abilityModifierForScore: (int)score {
return (int)floor((score - 10) / 2.0);
}
+(int)hitDieForSize: (NSString*)size{
if ([kMonsterSizeTiny isEqualToString:size]) {
return 4;
} else if ([kMonsterSizeSmall isEqualToString:size]) {
return 6;
} else if ([kMonsterSizeMedium isEqualToString:size]) {
return 8;
} else if ([kMonsterSizeLarge isEqualToString:size]) {
return 10;
} else if ([kMonsterSizeHuge isEqualToString:size]) {
return 12;
} else if ([kMonsterSizeGargantuan isEqualToString:size]) {
return 20;
} else {
return 8;
}
}
-(id)init {
self = [super init];
self.name = @"";
self.size = @"";
return self;
}
-(id)initWithJSONString: (NSString*)jsonString andContext:(NSManagedObjectContext*)context {
NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
return [self initWithJSONData:jsonData andContext:context];
}
-(id)initWithJSONData: (NSData*)jsonData andContext:(NSManagedObjectContext*)context {
self = [self initWithContext:context];
NSDictionary *jsonRoot = [JSONHelper parseJSONDataAsDictionary:jsonData];
self.name = [JSONHelper readStringFromDictionary:jsonRoot forKey:@"name" withDefaultValue:@""];
self.size = [JSONHelper readStringFromDictionary:jsonRoot forKey:@"size" withDefaultValue:@""];
self.type = [JSONHelper readStringFromDictionary:jsonRoot forKey:@"type" withDefaultValue:@""];
self.subtype = [JSONHelper readStringFromDictionary:jsonRoot forKey:@"tag" withDefaultValue:@""];
self.hpText = [JSONHelper readStringFromDictionary:jsonRoot forKey:@"hpText" withDefaultValue:@""];
self.alignment = [JSONHelper readStringFromDictionary:jsonRoot forKey:@"alignment" withDefaultValue:@""];
self.armorType = [JSONHelper readStringFromDictionary:jsonRoot forKey:@"armorName" withDefaultValue:@""];
self.otherArmorDescription = [JSONHelper readStringFromDictionary:jsonRoot forKey:@"otherArmorDesc" withDefaultValue:@""];
self.strengthScore = [JSONHelper readIntFromDictionary:jsonRoot forKey:@"strPoints" withDefaultValue:10];
self.dexterityScore = [JSONHelper readIntFromDictionary:jsonRoot forKey:@"dexPoints" withDefaultValue:10];
self.constitutionScore = [JSONHelper readIntFromDictionary:jsonRoot forKey:@"conPoints" withDefaultValue:10];
self.intelligenceScore = [JSONHelper readIntFromDictionary:jsonRoot forKey:@"intPoints" withDefaultValue:10];
self.wisdomScore = [JSONHelper readIntFromDictionary:jsonRoot forKey:@"wisPoints" withDefaultValue:10];
self.charismaScore = [JSONHelper readIntFromDictionary:jsonRoot forKey:@"chaPoints" withDefaultValue:10];
self.shieldBonus = [JSONHelper readIntFromDictionary:jsonRoot forKey:@"shieldBonus" withDefaultValue:0];
self.hitDice = [JSONHelper readIntFromDictionary:jsonRoot forKey:@"hitDice" withDefaultValue:0];
self.customHP = [JSONHelper readBoolFromDictionary:jsonRoot forKey:@"customHP" withDefaultValue:NO];
return self;
}
-(id)initWithMonster:(Monster* _Nonnull)monster {
self = [self initWithContext:monster.managedObjectContext];
[self copyFromMonster:monster];
return self;
}
-(NSString*)meta {
// "${size} ${type} (${subtype}) ${alignment}"
NSMutableArray *parts = [NSMutableArray arrayWithCapacity:4];
if (![StringHelper isStringNilOrEmpty:self.size]) {
[parts addObject:self.size];
}
if (![StringHelper isStringNilOrEmpty:self.type]) {
[parts addObject:self.type];
}
if (![StringHelper isStringNilOrEmpty:self.subtype]) {
[parts addObject:[NSString stringWithFormat:@"(%@)", self.subtype]];
}
if (![StringHelper isStringNilOrEmpty:self.alignment]) {
[parts addObject:self.alignment];
}
return [parts componentsJoinedByString:@" "];
}
-(int)abilityScoreForAbilityScoreName: (NSString*)abilityScoreName {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(int)abilityModifierForAbilityScoreName: (NSString*)abilityScoreName {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(int)strengthModifier {
return [Monster abilityModifierForScore:self.strengthScore];
}
-(int)dexterityModifier {
return [Monster abilityModifierForScore:self.dexterityScore];
}
-(int)constitutionModifier {
return [Monster abilityModifierForScore:self.constitutionScore];
}
-(int)intelligenceModifier {
return [Monster abilityModifierForScore:self.intelligenceScore];
}
-(int)wisdomModifier {
return [Monster abilityModifierForScore:self.wisdomScore];
}
-(int)charismaModifier {
return [Monster abilityModifierForScore:self.charismaScore];
}
//getArmorClass
-(NSString*)armorClassDescription {
BOOL hasShield = [self shieldBonus] != 0;
NSString *armorType = [self armorType];
if ([StringHelper isStringNilOrEmpty:armorType] || [kArmorNameNone isEqualToString:armorType]) {
// 10 + dexMod + 2 for shieldBonus "15" or "17 (shield)"
int armorClass = kArmorClassUnarmored + self.dexterityModifier + self.shieldBonus;
return [NSString stringWithFormat:@"%d%@", armorClass, (hasShield ? @" (shield)" : @"")];
} else if ([kArmorNameNaturalArmor isEqualToString:armorType]) {
// 10 + dexMod + naturalArmorBonus + 2 for shieldBonus "16 (natural armor)" or "18 (natural armor, shield)"
int armorClass = kArmorClassUnarmored + self.dexterityModifier + self.naturalArmorBonus + self.shieldBonus;
return [NSString stringWithFormat:@"%d (natural armor%@)", armorClass, (hasShield ? @" (shield)" : @"")];
} else if ([kArmorNameMageArmor isEqualToString:armorType]) {
// 10 + dexMod + 2 for shield + 3 for mage armor "15 (18 with mage armor)" or 17 (shield, 20 with mage armor)
int armorClass = kArmorClassUnarmored + self.dexterityModifier + self.shieldBonus;
int armorClassWithMageArmor = kArmorClassMageArmor + self.dexterityModifier + self.shieldBonus;
return [NSString stringWithFormat:@"%d (%@%d with mage armor)", armorClass, (hasShield ? @"shield, " : @""), armorClassWithMageArmor];
} else if ([kArmorNamePadded isEqualToString:armorType]) {
// 11 + dexMod + 2 for shield "18 (padded armor, shield)"
int armorClass = kArmorClassPadded + self.dexterityModifier + self.shieldBonus;
return [NSString stringWithFormat:@"%d (padded%@)", armorClass, (hasShield ? @"shield, " : @"")];
} else if ([kArmorNameLeather isEqualToString:armorType]) {
// 11 + dexMod + 2 for shield "18 (leather, shield)"
int armorClass = kArmorClassLeather + self.dexterityModifier + self.shieldBonus;
return [NSString stringWithFormat:@"%d (leather%@)", armorClass, (hasShield ? @"shield, " : @"")];
} else if ([kArmorNameStuddedLeather isEqualToString:armorType]) {
// 12 + dexMod +2 for shield "17 (studded leather)"
int armorClass = kArmorClassStudded + self.dexterityModifier + self.shieldBonus;
return [NSString stringWithFormat:@"%d (studded leather%@)", armorClass, (hasShield ? @"shield, " : @"")];
} else if ([kArmorNameHide isEqualToString:armorType]) {
// 12 + Min(2, dexMod) + 2 for shield "12 (hide armor)"
int armorClass = kArmorClassHide + MIN(2, self.dexterityModifier) + self.shieldBonus;
return [NSString stringWithFormat:@"%d (hide%@)", armorClass, (hasShield ? @", shield" : @"")];
} else if ([kArmorNameChainShirt isEqualToString:armorType]) {
// 13 + Min(2, dexMod) + 2 for shield "12 (chain shirt)"
int armorClass = kArmorClassChainShirt + MIN(2, self.dexterityModifier) + self.shieldBonus;
return [NSString stringWithFormat:@"%d (chain shirt%@)", armorClass, (hasShield ? @", shield" : @"")];
} else if ([kArmorNameScaleMail isEqualToString:armorType]) {
// 14 + Min(2, dexMod) + 2 for shield "14 (scale mail)"
int armorClass = kArmorClassScaleMail + MIN(2, self.dexterityModifier) + self.shieldBonus;
return [NSString stringWithFormat:@"%d (scale mail%@)", armorClass, (hasShield ? @", shield" : @"")];
} else if ([kArmorNameBreastplate isEqualToString:armorType]) {
// 14 + Min(2, dexMod) + 2 for shield "16 (breastplate)"
int armorClass = kArmorClassBreastplate + MIN(2, self.dexterityModifier) + self.shieldBonus;
return [NSString stringWithFormat:@"%d (breastplate%@)", armorClass, (hasShield ? @", shield" : @"")];
} else if ([kArmorNameHalfPlate isEqualToString:armorType]) {
// 15 + Min(2, dexMod) + 2 for shield "17 (half plate)"
int armorClass = kArmorClassHalfPlate + MIN(2, self.dexterityModifier) + self.shieldBonus;
return [NSString stringWithFormat:@"%d (half plate%@)", armorClass, (hasShield ? @", shield" : @"")];
} else if ([kArmorNameRingMail isEqualToString:armorType]) {
// 14 + 2 for shield "14 (ring mail)
int armorClass = kArmorClassRingMail + self.shieldBonus;
return [NSString stringWithFormat:@"%d (ring mail%@)", armorClass, (hasShield ? @", shield" : @"")];
} else if ([kArmorNameChainMail isEqualToString:armorType]) {
// 16 + 2 for shield "16 (chain mail)"
int armorClass = kArmorClassChainMail + self.shieldBonus;
return [NSString stringWithFormat:@"%d (chain mail%@)", armorClass, (hasShield ? @", shield" : @"")];
} else if ([kArmorNameSplintMail isEqualToString:armorType]) {
// 17 + 2 for shield "17 (splint)"
int armorClass = kArmorClassSplintMail + self.shieldBonus;
return [NSString stringWithFormat:@"%d (splint%@)", armorClass, (hasShield ? @", shield" : @"")];
} else if ([kArmorNamePlateMail isEqualToString:armorType]) {
// 18 + 2 for shield "18 (plate)"
int armorClass = kArmorClassPlate + self.shieldBonus;
return [NSString stringWithFormat:@"%d (plate%@)", armorClass, (hasShield ? @", shield" : @"")];
} else if ([kArmorNameOther isEqualToString:armorType]) {
// pure string value shield check does nothing just copies the string from otherArmorDesc
return self.otherArmorDescription;
} else {
return @"";
}
}
//getHitPoints
-(NSString*)hitPointsDescription {
if (self.customHP) {
return self.hpText;
} else {
int dieSize = [Monster hitDieForSize:self.size];
int conMod = self.constitutionModifier;
int hpTotal = (int)MAX(1, ceil(dieSize + conMod + (self.hitDice - 1) * ((dieSize + 1) / 2.0 + conMod)));
int conBonus = conMod * self.hitDice;
return [NSString stringWithFormat:@"%d (%dd%d%+d)", hpTotal, self.hitDice, dieSize, conBonus];
}
}
-(NSString*)speedDescription {
if (self.hasCustomSpeed) {
return self.customSpeed;
} else {
NSMutableArray* parts = [[NSMutableArray alloc] init];
if (self.baseSpeed > 0) {
[parts addObject:[NSString stringWithFormat:@"%d ft.", self.baseSpeed]];
}
if (self.burrowSpeed > 0) {
[parts addObject:[NSString stringWithFormat:@"burrow %d ft.", self.burrowSpeed]];
}
if (self.climbSpeed > 0) {
[parts addObject:[NSString stringWithFormat:@"climb %d ft.", self.climbSpeed]];
}
if (self.flySpeed > 0) {
[parts addObject:[NSString stringWithFormat:@"fly %d ft.%@", self.flySpeed, self.canHover ? @" (hover)" : @""]];
}
if (self.swimSpeed > 0) {
[parts addObject:[NSString stringWithFormat:@"swim %d ft.", self.swimSpeed]];
}
return [parts componentsJoinedByString:@" "];
}
}
-(NSString*)strengthDescription {
return [NSString stringWithFormat:@"%d (%+d)", self.strengthScore, self.strengthModifier];
}
-(NSString*)dexterityDescription {
return [NSString stringWithFormat:@"%d (%+d)", self.dexterityScore, self.dexterityModifier];
}
-(NSString*)constitutionDescription {
return [NSString stringWithFormat:@"%d (%+d)", self.constitutionScore, self.constitutionModifier];
}
-(NSString*)intelligenceDescription {
return [NSString stringWithFormat:@"%d (%+d)", self.intelligenceScore, self.intelligenceModifier];
}
-(NSString*)wisdomDescription {
return [NSString stringWithFormat:@"%d (%+d)", self.wisdomScore, self.wisdomModifier];
}
-(NSString*)charismaDescription {
return [NSString stringWithFormat:@"%d (%+d)", self.charismaScore, self.charismaModifier];
}
-(NSSet*)savingThrows {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(void)addSavingThrow: (SavingThrow*)savingThrow {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(void)remvoeSavingThrow: (SavingThrow*)savingThrow {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(void)clearSavingThrows {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(int)proficiencyBonusForType:(NSString*)proficiencyType {
if ([kProficiencyTypeNone isEqualToString:proficiencyType]) {
return 0;
} else if ([kProficiencyTypeProficient isEqualToString:proficiencyType]) {
return self.proficiencyBonus;
} else if ([kProficiencyTypeExpertise isEqualToString:proficiencyType]) {
return self.proficiencyBonus * 2;
} else {
return 0;
}
}
+(NSString*)advantageLabelStringForType:(NSString*)advantageType {
if ([kAdvantageTypeNone isEqualToString:advantageType]) {
return @"";
} else if ([kAdvantageTypeAdvantage isEqualToString:advantageType]) {
return @"(A)";
} else if ([kAdvantageTypeDisadvantage isEqualToString:advantageType]) {
return @"(D)";
} else {
return @"";
}
}
-(NSString*)savingThrowsDescription {
NSMutableArray *parts = [[NSMutableArray alloc] init];
NSString *name;
NSString *advantage;
int bonus;
if (
![kProficiencyTypeNone isEqualToString:self.strengthSavingThrowProficiency] ||
![kAdvantageTypeNone isEqualToString:self.strengthSavingThrowAdvantage]
) {
name = NSLocalizedString(@"Strength", @"");
bonus = self.strengthModifier + [self proficiencyBonusForType:self.strengthSavingThrowProficiency];
advantage = [Monster advantageLabelStringForType:self.strengthSavingThrowAdvantage];
if (advantage) {
advantage = [@" " stringByAppendingString:advantage];
}
[parts addObject:[NSString stringWithFormat:@"%@ %+d%@", name, bonus, advantage]];
}
if (
![kProficiencyTypeNone isEqualToString:self.dexteritySavingThrowProficiency] ||
![kAdvantageTypeNone isEqualToString:self.dexteritySavingThrowAdvantage]
) {
name = NSLocalizedString(@"Dexterity", @"");
bonus = self.dexterityModifier + [self proficiencyBonusForType:self.dexteritySavingThrowProficiency];
advantage = [Monster advantageLabelStringForType:self.dexteritySavingThrowAdvantage];
if (advantage) {
advantage = [@" " stringByAppendingString:advantage];
}
[parts addObject:[NSString stringWithFormat:@"%@ %+d%@", name, bonus, advantage]];
}
if (
![kProficiencyTypeNone isEqualToString:self.constitutionSavingThrowProficiency] ||
![kAdvantageTypeNone isEqualToString:self.constitutionSavingThrowAdvantage]
) {
name = NSLocalizedString(@"Constitution", @"");
bonus = self.constitutionModifier + [self proficiencyBonusForType:self.constitutionSavingThrowProficiency];
advantage = [Monster advantageLabelStringForType:self.constitutionSavingThrowAdvantage];
if (advantage) {
advantage = [@" " stringByAppendingString:advantage];
}
[parts addObject:[NSString stringWithFormat:@"%@ %+d%@", name, bonus, advantage]];
}
if (
![kProficiencyTypeNone isEqualToString:self.intelligenceSavingThrowProficiency] ||
![kAdvantageTypeNone isEqualToString:self.intelligenceSavingThrowAdvantage]
) {
name = NSLocalizedString(@"Intelligence", @"");
bonus = self.intelligenceModifier + [self proficiencyBonusForType:self.intelligenceSavingThrowProficiency];
advantage = [Monster advantageLabelStringForType:self.intelligenceSavingThrowAdvantage];
if (advantage) {
advantage = [@" " stringByAppendingString:advantage];
}
[parts addObject:[NSString stringWithFormat:@"%@ %+d%@", name, bonus, advantage]];
}
if (
![kProficiencyTypeNone isEqualToString:self.wisdomSavingThrowProficiency] ||
![kAdvantageTypeNone isEqualToString:self.wisdomSavingThrowAdvantage]
) {
name = NSLocalizedString(@"Wisdom", @"");
bonus = self.wisdomModifier + [self proficiencyBonusForType:self.wisdomSavingThrowProficiency];
advantage = [Monster advantageLabelStringForType:self.wisdomSavingThrowAdvantage];
if (advantage) {
advantage = [@" " stringByAppendingString:advantage];
}
[parts addObject:[NSString stringWithFormat:@"%@ %+d%@", name, bonus, advantage]];
}
if (
![kProficiencyTypeNone isEqualToString:self.charismaSavingThrowProficiency] ||
![kAdvantageTypeNone isEqualToString:self.charismaSavingThrowAdvantage]
) {
name = NSLocalizedString(@"Charisma", @"");
bonus = self.charismaModifier + [self proficiencyBonusForType:self.charismaSavingThrowProficiency];
advantage = [Monster advantageLabelStringForType:self.charismaSavingThrowAdvantage];
if (advantage) {
advantage = [@" " stringByAppendingString:advantage];
}
[parts addObject:[NSString stringWithFormat:@"%@ %+d%@", name, bonus, advantage]];
}
return [parts componentsJoinedByString:@", "];
}
-(int)proficiencyBonus {
if ([@"*" isEqualToString:challengeRating]) {
return customProficiencyBonus;
} else if (
[@"0" isEqualToString:challengeRating] ||
[@"1/8" isEqualToString:challengeRating] ||
[@"1/4" isEqualToString:challengeRating] ||
[@"1/2" isEqualToString:challengeRating] ||
[@"1" isEqualToString:challengeRating] ||
[@"2" isEqualToString:challengeRating] ||
[@"3" isEqualToString:challengeRating] ||
[@"4" isEqualToString:challengeRating]
) {
return 2;
} else if (
[@"5" isEqualToString:challengeRating] ||
[@"6" isEqualToString:challengeRating] ||
[@"7" isEqualToString:challengeRating] ||
[@"8" isEqualToString:challengeRating])
{
return 3;
} else if (
[@"9" isEqualToString:challengeRating] ||
[@"10" isEqualToString:challengeRating] ||
[@"11" isEqualToString:challengeRating] ||
[@"12" isEqualToString:challengeRating])
{
return 4;
} else if (
[@"13" isEqualToString:challengeRating] ||
[@"14" isEqualToString:challengeRating] ||
[@"15" isEqualToString:challengeRating] ||
[@"16" isEqualToString:challengeRating])
{
return 5;
} else if (
[@"17" isEqualToString:challengeRating] ||
[@"18" isEqualToString:challengeRating] ||
[@"19" isEqualToString:challengeRating] ||
[@"20" isEqualToString:challengeRating])
{
return 6;
} else if (
[@"21" isEqualToString:challengeRating] ||
[@"22" isEqualToString:challengeRating] ||
[@"23" isEqualToString:challengeRating] ||
[@"24" isEqualToString:challengeRating])
{
return 7;
} else if (
[@"25" isEqualToString:challengeRating] ||
[@"26" isEqualToString:challengeRating] ||
[@"27" isEqualToString:challengeRating] ||
[@"28" isEqualToString:challengeRating])
{
return 8;
} else if (
[@"29" isEqualToString:challengeRating] ||
[@"30" isEqualToString:challengeRating])
{
return 9;
} else {
return 0;
}
}
-(void)addSkill: (Skill*)skill {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(void)removeSkill: (Skill*)skill {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(void)clearSkills {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(NSString*)skillsDescription {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(void)addDamageType: (DamageType*)damageType {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(void)removeDamageType: (DamageType*)damageType {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(void)clearDamageTypes {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(NSString*)damageImmunitiesDescription {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(NSString*)damageResistancesDescription {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(NSString*)damageVulnerabilitiesDescription {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(void)addConditionImmunity: (NSString*)condition {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(void)removeConditionImmunity: (NSString*)condition {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(void)clearConditionImmunities {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(NSString*)conditionImmunitiesDescription {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(NSString*)sensesDescription {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(void)addLanguage: (Language*)language {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(void)removeLanguage: (Language*)language {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(void)clearLanguages {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(NSString*)languagesDescription {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(NSString*)challengeRatingDescription {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(void)addAbility: (Ability*)ability {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(void)removeAbility: (Ability*)ability {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(void)clearAbilities {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(NSArray*)abilityDescriptions {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(void)addAction: (Action*)action {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(void)removeAction: (Action*)action {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(void)clearActions {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(NSArray*)actionDescriptions {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(NSString*)placeholderReplacedText: (NSString*)text {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(int)savingThrowForAbilityScoreName: (NSString*)abilityScoreName {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(int)spellSaveDCForAbilityScoreName: (NSString*)abilityScoreName {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(int)attackBonusForAbilityScoreName: (NSString*)abilityScoreName {
@throw [[NSException alloc] initWithName:@"unimplemented" reason:@"Method not implemented." userInfo:nil];
}
-(void)copyFromMonster:(Monster*)monster {
self.name = monster.name;
self.size = monster.size;
self.type = monster.type;
self.subtype = monster.subtype;
self.alignment = monster.alignment;
self.strengthScore = monster.strengthScore;
self.dexterityScore = monster.dexterityScore;
self.constitutionScore = monster.constitutionScore;
self.intelligenceScore = monster.intelligenceScore;
self.wisdomScore = monster.wisdomScore;
self.charismaScore = monster.charismaScore;
self.armorType = monster.armorType;
self.otherArmorDescription = monster.otherArmorDescription;
self.shieldBonus = monster.shieldBonus;
self.customHP = monster.customHP;
self.hitDice = monster.hitDice;
self.hpText = monster.hpText;
self.baseSpeed = monster.baseSpeed;
self.burrowSpeed = monster.burrowSpeed;
self.climbSpeed = monster.climbSpeed;
self.flySpeed = monster.flySpeed;
self.canHover = monster.canHover;
self.swimSpeed = monster.swimSpeed;
self.hasCustomSpeed = monster.hasCustomSpeed;
self.customSpeed = monster.customSpeed;
self.armorType = monster.armorType;
self.naturalArmorBonus = monster.naturalArmorBonus;
self.hasShield = monster.hasShield;
self.customArmor = monster.customArmor;
self.strengthSavingThrowAdvantage = monster.strengthSavingThrowAdvantage;
self.strengthSavingThrowProficiency = monster.strengthSavingThrowProficiency;
self.dexteritySavingThrowAdvantage = monster.dexteritySavingThrowAdvantage;
self.dexteritySavingThrowProficiency = monster.dexteritySavingThrowProficiency;
self.constitutionSavingThrowAdvantage = monster.constitutionSavingThrowAdvantage;
self.constitutionSavingThrowProficiency = monster.constitutionSavingThrowProficiency;
self.intelligenceSavingThrowAdvantage = monster.intelligenceSavingThrowAdvantage;
self.intelligenceSavingThrowProficiency = monster.intelligenceSavingThrowProficiency;
self.wisdomSavingThrowAdvantage = monster.wisdomSavingThrowAdvantage;
self.wisdomSavingThrowProficiency = monster.wisdomSavingThrowProficiency;
self.charismaSavingThrowAdvantage = monster.charismaSavingThrowAdvantage;
self.charismaSavingThrowProficiency = monster.charismaSavingThrowProficiency;
}
@end

View File

@@ -0,0 +1,27 @@
//
// ProficiencyType.swift
// MonsterCards
//
// Created by Tom Hicks on 1/17/21.
//
import Foundation
enum ProficiencyType: String, CaseIterable, Identifiable {
case none = "none"
case proficient = "proficient"
case expertise = "expertise"
var id: ProficiencyType { self }
var displayName: String {
switch self {
case .none:
return "None"
case .proficient:
return "Proficient"
case .expertise:
return "Expertise"
}
}
}

View File

@@ -1,22 +0,0 @@
//
// SavingThrow.h
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface SavingThrow : NSObject
@property NSString* name;
@property int order;
-(id)initWithName: (NSString*)name andOrder: (int)order;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,31 +0,0 @@
//
// SavingThrow.m
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "SavingThrow.h"
@implementation SavingThrow
-(id)init {
self = [super init];
self.name = @"";
self.order = -1;
return self;
}
-(id)initWithName: (NSString*)name andOrder: (int)order {
self = [super init];
self.name = name;
self.order = order;
return self;
}
@end

View File

@@ -1,27 +0,0 @@
//
// Skill.h
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Monster.h"
NS_ASSUME_NONNULL_BEGIN
@class Monster;
@interface Skill : NSObject
@property NSString* name;
@property NSString* abilityScoreName;
@property NSString* notes;
-(id)initWithName: (NSString*)name abilityScoreName:(NSString*)abilityScoreName andNotes:(NSString*)notes;
-(int)skillBonusForMonster: (Monster*)monster;
-(NSString*)textForMonster: (Monster*)monster;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,50 +0,0 @@
//
// Skill.m
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "Skill.h"
@implementation Skill
-(id)init {
self = [super init];
self.name = @"";
self.abilityScoreName = @"";
self.notes = @"";
return self;
}
-(id)initWithName: (NSString*)name abilityScoreName:(NSString*)abilityScoreName andNotes:(NSString*)notes{
self = [super init];
self.name = name;
self.abilityScoreName = abilityScoreName;
self.notes = notes;
return self;
}
-(int)skillBonusForMonster: (Monster*)monster {
int bonus = [monster abilityModifierForAbilityScoreName: self.abilityScoreName];
if ([@" (ex)" isEqualToString:self.notes]) {
bonus += 2 * monster.proficiencyBonus;
} else {
bonus += monster.proficiencyBonus;
}
return bonus;
}
-(NSString*)textForMonster: (Monster*)monster {
int bonus = [self skillBonusForMonster:monster];
// [self.name localizedUppercaseString]
return [NSString stringWithFormat:@"%@%@ %d", [[self.name substringToIndex:1] localizedUppercaseString], [self.name substringFromIndex:1], bonus];
}
@end

View File

@@ -1,48 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17192" systemVersion="19H2" minimumToolsVersion="Automatic" sourceLanguage="Objective-C" usedWithCloudKit="YES" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17192" systemVersion="19H114" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier="">
<entity name="Monster" representedClassName="Monster" syncable="YES" codeGenerationType="category">
<attribute name="alignment" attributeType="String" defaultValueString=""/>
<attribute name="armorType" attributeType="String" defaultValueString="none"/>
<attribute name="baseSpeed" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="burrowSpeed" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="armorType" attributeType="String" defaultValueString=""/>
<attribute name="baseSpeed" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="blindsightDistance" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="burrowSpeed" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="canHover" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="charismaSavingThrowAdvantage" attributeType="String" defaultValueString="none"/>
<attribute name="challengeRating" attributeType="String" defaultValueString=""/>
<attribute name="charismaSavingThrowAdvantage" attributeType="String" defaultValueString=""/>
<attribute name="charismaSavingThrowProficiency" attributeType="String" defaultValueString="none"/>
<attribute name="charismaScore" attributeType="Integer 16" defaultValueString="10" usesScalarValueType="YES"/>
<attribute name="climbSpeed" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="charismaScore" attributeType="Integer 64" defaultValueString="10" usesScalarValueType="YES"/>
<attribute name="climbSpeed" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="constitutionSavingThrowAdvantage" attributeType="String" defaultValueString="none"/>
<attribute name="constitutionSavingThrowProficiency" attributeType="String" defaultValueString="none"/>
<attribute name="constitutionScore" attributeType="Integer 16" defaultValueString="10" usesScalarValueType="YES"/>
<attribute name="constitutionScore" attributeType="Integer 64" defaultValueString="10" usesScalarValueType="YES"/>
<attribute name="customArmor" attributeType="String" defaultValueString=""/>
<attribute name="customHP" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="customHP" attributeType="String" defaultValueString=""/>
<attribute name="customProficiencyBonus" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="customSpeed" attributeType="String" defaultValueString=""/>
<attribute name="darkvisionDistance" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="dexteritySavingThrowAdvantage" attributeType="String" defaultValueString="none"/>
<attribute name="dexteritySavingThrowProficiency" attributeType="String" defaultValueString="none" customClassName="none"/>
<attribute name="dexterityScore" attributeType="Integer 16" defaultValueString="10" usesScalarValueType="YES"/>
<attribute name="flySpeed" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="dexteritySavingThrowProficiency" attributeType="String" defaultValueString="none"/>
<attribute name="dexterityScore" attributeType="Integer 64" defaultValueString="10" usesScalarValueType="YES"/>
<attribute name="flySpeed" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="hasCustomHP" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="hasCustomSpeed" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="hasShield" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="hitDice" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="hpText" attributeType="String" defaultValueString=""/>
<attribute name="hitDice" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="intelligenceSavingThrowAdvantage" attributeType="String" defaultValueString="none"/>
<attribute name="intelligenceSavingThrowProficiency" attributeType="String" defaultValueString="none"/>
<attribute name="intelligenceScore" attributeType="Integer 16" defaultValueString="10" usesScalarValueType="YES"/>
<attribute name="name" attributeType="String" defaultValueString=""/>
<attribute name="natrualArmorBonus" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="intelligenceScore" attributeType="Integer 64" defaultValueString="10" usesScalarValueType="YES"/>
<attribute name="isBlind" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="name" attributeType="String" defaultValueString="Unnamed Monster"/>
<attribute name="naturalArmorBonus" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="otherArmorDescription" attributeType="String" defaultValueString=""/>
<attribute name="shieldBonus" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="shieldBonus" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="size" attributeType="String" defaultValueString=""/>
<attribute name="strengthSavingThrowAdvantage" attributeType="String" defaultValueString="none"/>
<attribute name="strengthSavingThrowProficiency" attributeType="String" defaultValueString="none"/>
<attribute name="strengthScore" attributeType="Integer 16" defaultValueString="10" usesScalarValueType="YES"/>
<attribute name="strengthScore" attributeType="Integer 64" defaultValueString="10" usesScalarValueType="YES"/>
<attribute name="subtype" attributeType="String" defaultValueString=""/>
<attribute name="swimSpeed" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="swimSpeed" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="tremorsenseDistance" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="truesightDistance" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="type" attributeType="String" defaultValueString=""/>
<attribute name="understandsBut" attributeType="String" defaultValueString=""/>
<attribute name="wisdomSavingThrowAdvantage" attributeType="String" defaultValueString="none"/>
<attribute name="wisdomSavingThrowProficiency" attributeType="String" defaultValueString="none"/>
<attribute name="wisdomScore" attributeType="Integer 16" defaultValueString="10" usesScalarValueType="YES"/>
<attribute name="wisdomScore" attributeType="Integer 64" defaultValueString="10" usesScalarValueType="YES"/>
</entity>
<elements>
<element name="Monster" positionX="-63" positionY="-18" width="128" height="643"/>
<element name="Monster" positionX="-63" positionY="-18" width="128" height="763"/>
</elements>
</model>

View File

@@ -0,0 +1,20 @@
//
// MonsterCardsApp.swift
// MonsterCards
//
// Created by Tom Hicks on 1/15/21.
//
import SwiftUI
@main
struct MonsterCardsApp: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}

View File

@@ -0,0 +1,58 @@
//
// Persistence.swift
// MonsterCards
//
// Created by Tom Hicks on 1/15/21.
//
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
let monsters: [Monster] = [
Monster(context:viewContext, name: "Ted", size: "Huge", type: "humanoid", subtype: "any race", alignment: "any alignment"),
Monster(context:viewContext, name: "Steve", size: "Huge", type: "humanoid", subtype: "any race", alignment: "any alignment"),
Monster(context:viewContext, name: "Dave", size: "Huge", type: "humanoid", subtype: "any race", alignment: "any alignment")
]
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
// fatalError("TOMHICKS_Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
let container: NSPersistentCloudKitContainer
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "MonsterCards")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// NSPersistentStoreCoordinator.destroyPersistentStore(storeDes)
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -1,16 +0,0 @@
//
// SceneDelegate.h
// MonsterCards
//
// Created by Tom Hicks on 9/2/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
@property (strong, nonatomic) UIWindow * window;
@end

View File

@@ -1,62 +0,0 @@
//
// SceneDelegate.m
// MonsterCards
//
// Created by Tom Hicks on 9/2/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "SceneDelegate.h"
#import "AppDelegate.h"
@interface SceneDelegate ()
@end
@implementation SceneDelegate
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
}
- (void)sceneDidDisconnect:(UIScene *)scene {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
}
- (void)sceneDidBecomeActive:(UIScene *)scene {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
- (void)sceneWillResignActive:(UIScene *)scene {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
- (void)sceneWillEnterForeground:(UIScene *)scene {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
- (void)sceneDidEnterBackground:(UIScene *)scene {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
// Save changes in the application's managed object context when the application transitions to the background.
[(AppDelegate *)UIApplication.sharedApplication.delegate saveContext];
}
@end

View File

@@ -0,0 +1,21 @@
//
// Collections.swift
// MonsterCards
//
// Created by Tom Hicks on 1/15/21.
//
import SwiftUI
struct Collections: View {
// @State var allCollections: [MonsterCollection] = []
var body: some View {
Text("Collections")
}
}
struct Collections_Previews: PreviewProvider {
static var previews: some View {
Collections().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}

View File

@@ -1,17 +0,0 @@
//
// CollectionsViewController.h
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface CollectionsViewController : UIViewController
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,32 +0,0 @@
//
// CollectionsViewController.m
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "CollectionsViewController.h"
@interface CollectionsViewController ()
@end
@implementation CollectionsViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end

View File

@@ -0,0 +1,106 @@
//
// ContentView.swift
// MonsterCards
//
// Created by Tom Hicks on 1/15/21.
//
import SwiftUI
import CoreData
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
var body: some View {
TabView {
Search()
.tabItem {
Image(systemName: "magnifyingglass")
Text("Search")
}
Dashboard()
.tabItem {
Image(systemName: "rectangle.3.offgrid.fill")
Text("Dashboard")
}
Collections()
.tabItem {
Image(systemName: "tray.full.fill")
Text("Collections")
}
Library()
.tabItem {
Image(systemName: "book.fill")
Text("Library")
}
}
}
// @FetchRequest(
// sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
// animation: .default)
// private var items: FetchedResults<Item>
//
// var body: some View {
// List {
// ForEach(items) { item in
// Text("Item at \(item.timestamp!, formatter: itemFormatter)")
// }
// .onDelete(perform: deleteItems)
// }
// .toolbar {
// #if os(iOS)
// EditButton()
// #endif
//
// Button(action: addItem) {
// Label("Add Item", systemImage: "plus")
// }
// }
// }
//
// private func addItem() {
// withAnimation {
// let newItem = Item(context: viewContext)
// newItem.timestamp = Date()
//
// do {
// try viewContext.save()
// } catch {
// // Replace this implementation with code to handle the error appropriately.
// // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
// let nsError = error as NSError
// fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
// }
// }
// }
//
// private func deleteItems(offsets: IndexSet) {
// withAnimation {
// offsets.map { items[$0] }.forEach(viewContext.delete)
//
// do {
// try viewContext.save()
// } catch {
// // Replace this implementation with code to handle the error appropriately.
// // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
// let nsError = error as NSError
// fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
// }
// }
// }
}
//private let itemFormatter: DateFormatter = {
// let formatter = DateFormatter()
// formatter.dateStyle = .short
// formatter.timeStyle = .medium
// return formatter
//}()
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}

View File

@@ -0,0 +1,20 @@
//
// Dashboard.swift
// MonsterCards
//
// Created by Tom Hicks on 1/15/21.
//
import SwiftUI
struct Dashboard: View {
var body: some View {
Text("Dashboard")
}
}
struct Dashboard_Previews: PreviewProvider {
static var previews: some View {
Dashboard().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}

View File

@@ -1,17 +0,0 @@
//
// DashboardViewController.h
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface DashboardViewController : UIViewController
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,32 +0,0 @@
//
// DashboardViewController.m
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "DashboardViewController.h"
@interface DashboardViewController ()
@end
@implementation DashboardViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end

View File

@@ -0,0 +1,425 @@
//
// EditMonster.swift
// MonsterCards
//
// Created by Tom Hicks on 1/16/21.
//
import CoreData
import SwiftUI
struct EditMonster: View {
// TODO: add challengeRating/challengeRatingEnum and customChallengeRating maybe in basicInfo
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
@Environment(\.managedObjectContext) private var viewContext
var monster: Monster
@State private var monsterName: String = ""
@State private var monsterSize: String = ""
@State private var monsterType: String = ""
@State private var monsterSubtype: String = ""
@State private var monsterAlignment: String = ""
@State private var monsterHitDice: Int64 = 0
@State private var monsterHasCustomHP: Bool = false
@State private var monsterCustomHP: String = ""
@State private var monsterArmorType: String = ""
@State private var monsterHasShield: Bool = false
@State private var monsterNaturalArmorBonus: Int64 = 0
@State private var monsterCustomArmor: String = ""
@State private var monsterBaseSpeed: Int64 = 0
@State private var monsterBurrowSpeed: Int64 = 0
@State private var monsterClimbSpeed: Int64 = 0
@State private var monsterFlySpeed: Int64 = 0
@State private var monsterCanHover: Bool = false
@State private var monsterSwimSpeed: Int64 = 0
@State private var monsterHasCustomSpeed: Bool = false
@State private var monsterCustomSpeed: String = ""
@State private var monsterStrengthScore: Int64 = 10
@State private var monsterDexterityScore: Int64 = 10
@State private var monsterConstitutionScore: Int64 = 10
@State private var monsterIntelligenceScore: Int64 = 10
@State private var monsterWisdomScore: Int64 = 10
@State private var monsterCharismaScore: Int64 = 10
@State private var monsterStrengthSavingThrowProficiency: ProficiencyType = .none
@State private var monsterStrengthSavingThrowAdvantage: AdvantageType = .none
@State private var monsterDexteritySavingThrowProficiency: ProficiencyType = .none
@State private var monsterDexteritySavingThrowAdvantage: AdvantageType = .none
@State private var monsterConstitutionSavingThrowProficiency: ProficiencyType = .none
@State private var monsterConstitutionSavingThrowAdvantage: AdvantageType = .none
@State private var monsterIntelligenceSavingThrowProficiency: ProficiencyType = .none
@State private var monsterIntelligenceSavingThrowAdvantage: AdvantageType = .none
@State private var monsterWisdomSavingThrowProficiency: ProficiencyType = .none
@State private var monsterWisdomSavingThrowAdvantage: AdvantageType = .none
@State private var monsterCharismaSavingThrowProficiency: ProficiencyType = .none
@State private var monsterCharismaSavingThrowAdvantage: AdvantageType = .none
var body: some View {
List {
Section(header: Text("Basic Info")) {
// Editable Text field bound to monster.name
MCTextField(
label: "Name",
value: $monsterName)
// Editable Text field bound to monster.size
MCTextField(
label: "Size",
value: $monsterSize)
// Editable Text field bound to monster.type
MCTextField(
label: "Type",
value: $monsterType)
// Editable Text field bound to monster.subType
MCTextField(
label: "Subtype",
value: $monsterSubtype)
// Editable Text field bound to monster.alignment
MCTextField(
label: "Alignment",
value: $monsterAlignment)
// Number with -/+ buttons bound to monster.hitDice
MCStepperField(
label: "Hit Dice",
value: $monsterHitDice)
// Toggle bound to monster.hasCustomHP?
Toggle(
"Has Custom HP",
isOn:$monsterHasCustomHP)
// Editable Text field bound to monster.customHpText?
MCTextField(
label: "Custom HP",
value: $monsterCustomHP)
}
.textCase(nil)
Section(header: Text("Armor")) {
// Armor Type select bound to monster.armorType?
// TODO: this should be a select/dropdown
MCTextField(
label: "Armor Type",
value: $monsterArmorType)
// Toggle bound to monster.hasShield?
Toggle(
"Has Shield",
isOn: $monsterHasShield)
// Number with -/+ buttons bound to monster.naturalArmorBonus
MCStepperField(
label: "Natural Armor Bonus",
value: $monsterNaturalArmorBonus)
// Editable Text field bound to monster.customArmorText?
MCTextField(
label: "Custom Armor",
value: $monsterCustomArmor)
}
.textCase(nil)
Section(header: Text("Speed")) {
// Number bound to monster.baseSpeed
MCStepperField(
label: "Base",
step: 5,
suffix: " ft.",
value: $monsterBaseSpeed)
// Number bound to monster.burrowSpeed
MCStepperField(
label: "Burrow",
step: 5,
suffix: " ft.",
value: $monsterBurrowSpeed)
// Number bound to monster.climbSpeed
MCStepperField(
label: "Climb",
step: 5,
suffix: " ft.",
value: $monsterClimbSpeed)
// Number bound to monster.flySpeed
MCStepperField(
label: "Fly",
step: 5,
suffix: " ft.",
value: $monsterFlySpeed)
// Toggle bound to monster.canHover
Toggle(
"Can Hover",
isOn: $monsterCanHover)
// Number bound to monster.swimSpeed
MCStepperField(
label: "Swim",
step: 5,
suffix: " ft.",
value: $monsterSwimSpeed)
// Toggle bound to monster.hasCustomSpeed
Toggle(
"Has Custom Speed",
isOn: $monsterHasCustomSpeed)
// Editable Text field bound to monster.customSpeedText
MCTextField(
label: "Custom Speed",
value: $monsterCustomSpeed)
}
.textCase(nil)
Section(header: Text("Ability Scores")) {
MCStepperField(
label: "STR",
value: $monsterStrengthScore)
MCStepperField(
label: "DEX",
value: $monsterDexterityScore)
MCStepperField(
label: "CON",
value: $monsterConstitutionScore)
MCStepperField(
label: "INT",
value: $monsterIntelligenceScore)
MCStepperField(
label: "WIS",
value: $monsterWisdomScore)
MCStepperField(
label: "CHA",
value: $monsterCharismaScore)
}
.textCase(nil)
Section(header: Text("Saving Throws")) {
VStack {
MCAdvantagePicker(
label: "Strength Advantage",
value: $monsterStrengthSavingThrowAdvantage)
MCProficiencyPicker(
label: "Strength Proficiency",
value: $monsterStrengthSavingThrowProficiency)
}
VStack {
MCAdvantagePicker(
label: "Dexterity Advantage",
value: $monsterDexteritySavingThrowAdvantage)
MCProficiencyPicker(
label: "Dexterity Proficiency",
value: $monsterDexteritySavingThrowProficiency)
}
VStack {
MCAdvantagePicker(
label: "Constitution Advantage",
value: $monsterConstitutionSavingThrowAdvantage)
MCProficiencyPicker(
label: "Constitution Proficiency",
value: $monsterConstitutionSavingThrowProficiency)
}
VStack {
MCAdvantagePicker(
label: "Intelligence Advantage",
value: $monsterIntelligenceSavingThrowAdvantage)
MCProficiencyPicker(
label: "Intelligence Proficiency",
value: $monsterIntelligenceSavingThrowProficiency)
}
VStack {
MCAdvantagePicker(
label: "Wisdom Advantage",
value: $monsterWisdomSavingThrowAdvantage)
MCProficiencyPicker(
label: "Wisdom Proficiency",
value: $monsterWisdomSavingThrowProficiency)
}
VStack {
MCAdvantagePicker(
label: "Charisma Advantage",
value: $monsterCharismaSavingThrowAdvantage)
MCProficiencyPicker(
label: "Charisma Proficiency",
value: $monsterCharismaSavingThrowProficiency)
}
}
.textCase(nil)
}
.onAppear(perform: copyMonsterToLocal)
.toolbar(content: {
ToolbarItem(placement: .primaryAction) {
Button("Save", action: saveMonster)
}
ToolbarItem(placement: ToolbarItemPlacement.cancellationAction) {
Button("Cancel", action: cancel)
}
})
.navigationTitle(monster.name ?? "")
.navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden(true)
}
private func dismissView() {
self.presentationMode.wrappedValue.dismiss()
}
private func saveMonster() {
copyLocalToMonster()
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
// TODO: save coredata context
dismissView()
}
private func cancel() {
dismissView()
}
private func copyMonsterToLocal() {
monsterName = monster.name ?? ""
monsterSize = monster.size ?? ""
monsterType = monster.type ?? ""
monsterSubtype = monster.subtype ?? ""
monsterAlignment = monster.alignment ?? ""
monsterHitDice = monster.hitDice
monsterHasCustomHP = monster.hasCustomHP
monsterCustomHP = monster.customHP ?? ""
monsterArmorType = monster.armorType ?? ""
monsterHasShield = monster.hasShield
monsterNaturalArmorBonus = monster.naturalArmorBonus
monsterCustomArmor = monster.customArmor ?? ""
monsterBaseSpeed = monster.baseSpeed
monsterBurrowSpeed = monster.burrowSpeed
monsterClimbSpeed = monster.climbSpeed
monsterFlySpeed = monster.flySpeed
monsterCanHover = monster.canHover
monsterSwimSpeed = monster.swimSpeed
monsterHasCustomSpeed = monster.hasCustomSpeed
monsterCustomSpeed = monster.customSpeed ?? ""
monsterStrengthScore = monster.strengthScore
monsterDexterityScore = monster.dexterityScore
monsterConstitutionScore = monster.constitutionScore
monsterIntelligenceScore = monster.intelligenceScore
monsterWisdomScore = monster.wisdomScore
monsterCharismaScore = monster.charismaScore
monsterStrengthSavingThrowProficiency = ProficiencyType.init(rawValue: monster.strengthSavingThrowProficiency ?? "") ?? .none
monsterStrengthSavingThrowAdvantage = AdvantageType(rawValue: monster.strengthSavingThrowAdvantage ?? "") ?? .none
monsterDexteritySavingThrowProficiency = ProficiencyType(rawValue: monster.dexteritySavingThrowProficiency ?? "") ?? .none
monsterDexteritySavingThrowAdvantage = AdvantageType(rawValue: monster.dexteritySavingThrowAdvantage ?? "") ?? .none
monsterConstitutionSavingThrowProficiency = ProficiencyType(rawValue: monster.constitutionSavingThrowProficiency ?? "") ?? .none
monsterConstitutionSavingThrowAdvantage = AdvantageType(rawValue: monster.constitutionSavingThrowAdvantage ?? "") ?? .none
monsterIntelligenceSavingThrowProficiency = ProficiencyType(rawValue: monster.intelligenceSavingThrowProficiency ?? "") ?? .none
monsterIntelligenceSavingThrowAdvantage = AdvantageType(rawValue: monster.intelligenceSavingThrowAdvantage ?? "") ?? .none
monsterWisdomSavingThrowProficiency = ProficiencyType(rawValue: monster.wisdomSavingThrowProficiency ?? "") ?? .none
monsterWisdomSavingThrowAdvantage = AdvantageType(rawValue: monster.wisdomSavingThrowAdvantage ?? "") ?? .none
monsterCharismaSavingThrowProficiency = ProficiencyType(rawValue: monster.charismaSavingThrowProficiency ?? "") ?? .none
monsterCharismaSavingThrowAdvantage = AdvantageType(rawValue: monster.charismaSavingThrowAdvantage ?? "") ?? .none
}
private func copyLocalToMonster() {
monster.name = monsterName
monster.size = monsterSize
monster.type = monsterType
monster.subtype = monsterSubtype
monster.alignment = monsterAlignment
monster.hitDice = monsterHitDice
monster.hasCustomHP = monsterHasCustomHP
monster.customHP = monsterCustomHP
monster.armorType = monsterArmorType
monster.hasShield = monsterHasShield
monster.naturalArmorBonus = monsterNaturalArmorBonus
monster.customArmor = monsterCustomArmor
monster.baseSpeed = monsterBaseSpeed
monster.burrowSpeed = monsterBurrowSpeed
monster.climbSpeed = monsterClimbSpeed
monster.flySpeed = monsterFlySpeed
monster.canHover = monsterCanHover
monster.swimSpeed = monsterSwimSpeed
monster.hasCustomSpeed = monsterHasCustomSpeed
monster.customSpeed = monsterCustomSpeed
monster.strengthScore = monsterStrengthScore
monster.dexterityScore = monsterDexterityScore
monster.constitutionScore = monsterConstitutionScore
monster.intelligenceScore = monsterIntelligenceScore
monster.wisdomScore = monsterWisdomScore
monster.charismaScore = monsterCharismaScore
monster.strengthSavingThrowProficiency = monsterStrengthSavingThrowProficiency.rawValue
monster.strengthSavingThrowAdvantage = monsterStrengthSavingThrowAdvantage.rawValue
monster.dexteritySavingThrowProficiency = monsterDexteritySavingThrowProficiency.rawValue
monster.dexteritySavingThrowAdvantage = monsterDexteritySavingThrowAdvantage.rawValue
monster.constitutionSavingThrowProficiency = monsterConstitutionSavingThrowProficiency.rawValue
monster.constitutionSavingThrowAdvantage = monsterConstitutionSavingThrowAdvantage.rawValue
monster.intelligenceSavingThrowProficiency = monsterIntelligenceSavingThrowProficiency.rawValue
monster.intelligenceSavingThrowAdvantage = monsterIntelligenceSavingThrowAdvantage.rawValue
monster.wisdomSavingThrowProficiency = monsterWisdomSavingThrowProficiency.rawValue
monster.wisdomSavingThrowAdvantage = monsterWisdomSavingThrowAdvantage.rawValue
monster.charismaSavingThrowProficiency = monsterCharismaSavingThrowProficiency.rawValue
monster.charismaSavingThrowAdvantage = monsterCharismaSavingThrowAdvantage.rawValue
}
}
struct EditMonster_Previews: PreviewProvider {
static var previews: some View {
let context = PersistenceController.preview.container.viewContext
let monster = Monster.init(context: context)
monster.name = "Steve"
monster.size = "Medium"
monster.type = "humanoid"
monster.subtype = "human"
monster.alignment = "LG"
monster.hitDice = 6
monster.hasCustomHP = true
monster.customHP = "12 (1d10)+2"
monster.baseSpeed = 5
monster.burrowSpeed = 10
monster.climbSpeed = 15
monster.flySpeed = 20
monster.swimSpeed = 25
monster.canHover = true
monster.hasCustomSpeed = false
monster.customSpeed = "walk: 5 ft."
monster.strengthScore = 8
monster.dexterityScore = 10
monster.constitutionScore = 12
monster.intelligenceScore = 14
monster.wisdomScore = 16
monster.charismaScore = 18
monster.strengthSavingThrowAdvantage = AdvantageType.none.rawValue
monster.strengthSavingThrowProficiency = ProficiencyType.none.rawValue
monster.dexteritySavingThrowAdvantage = AdvantageType.advantage.rawValue
monster.dexteritySavingThrowProficiency = ProficiencyType.proficient.rawValue
monster.constitutionSavingThrowAdvantage = AdvantageType.disadvantage.rawValue
monster.constitutionSavingThrowProficiency = ProficiencyType.expertise.rawValue
monster.intelligenceSavingThrowAdvantage = AdvantageType.none.rawValue
monster.intelligenceSavingThrowProficiency = ProficiencyType.expertise.rawValue
monster.wisdomSavingThrowAdvantage = AdvantageType.advantage.rawValue
monster.wisdomSavingThrowProficiency = ProficiencyType.proficient.rawValue
monster.charismaSavingThrowAdvantage = AdvantageType.disadvantage.rawValue
monster.charismaSavingThrowProficiency = ProficiencyType.none.rawValue
return EditMonster(monster: monster).environment(\.managedObjectContext, context)
}
}

View File

@@ -1,22 +0,0 @@
//
// EditMonsterViewController.h
// MonsterCards
//
// Created by Tom Hicks on 9/8/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "Monster.h"
#import "MCShortStringFieldTableViewCell.h"
NS_ASSUME_NONNULL_BEGIN
@interface EditMonsterViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, MCFormFieldDelegate>
@property Monster* originalMonster;
@property (weak, nonatomic) IBOutlet UITableView *monsterTableView;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,719 +0,0 @@
//
// EditMonsterViewController.m
// MonsterCards
//
// Created by Tom Hicks on 9/8/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "EditMonsterViewController.h"
#import "MCBooleanFieldTableViewCell.h"
#import "MCIntegerFieldTableViewCell.h"
#import "MCRadioFieldTableViewCell.h"
#import "MCSelectFieldTableViewCell.h"
#import "MCShortStringFieldTableViewCell.h"
#import "AppDelegate.h"
@interface EditMonsterViewController ()
@property Monster* editingMonster;
@end
const int kSectionIndexBasicInfo = 0;
const int kSectionIndexArmor = 1;
const int kSectionIndexSpeed = 2;
const int kSectionIndexAbilityScores = 3;
const int kSectionIndexSavingThrows = 4;
const int kBasicInfoSectionRowIndexName = 0;
const int kBasicInfoSectionRowIndexSize = 1;
const int kBasicInfoSectionRowIndexType = 2;
const int kBasicInfoSectionRowIndexSubtype = 3;
const int kBasicInfoSectionRowIndexAlignment = 4;
const int kBasicInfoSectionRowIndexHitDice = 5;
const int kBasicInfoSectionRowIndexCustomHP = 6;
const int kBasicInfoSectionRowIndexCustomHPText = 7;
const int kArmorSectionRowIndexArmorType = 0;
const int kArmorSectionRowIndexHasShield = 1;
const int kArmorSectionRowIndexNaturalArmorBonus = 2;
const int kArmorSectionRowIndexCustomArmor = 3;
const int kSpeedSectionRowIndexBaseSpeed = 0;
const int kSpeedSectionRowIndexBurrowSpeed = 1;
const int kSpeedSectionRowIndexClimbSpeed = 2;
const int kSpeedSectionRowIndexFlySpeed = 3;
const int kSpeedSectionRowIndexCanHover = 4;
const int kSpeedSectionRowIndexSwimSpeed = 5;
const int kSpeedSectionRowIndexHasCustomSpeed = 6;
const int kSpeedSectionRowIndexCustomSpeed = 7;
const int kAbilityScoreSectionRowIndexStrength = 0;
const int kAbilityScoreSectionRowIndexDexterity = 1;
const int kAbilityScoreSectionRowIndexConstitution = 2;
const int kAbilityScoreSectionRowIndexIntelligence = 3;
const int kAbilityScoreSectionRowIndexWisdom = 4;
const int kAbilityScoreSectionRowIndexCharisma = 5;
const int kSavingThrowsSectionRowIndexStrengthProficiency = 0;
const int kSavingThrowsSectionRowIndexStrengthAdvantage = 1;
const int kSavingThrowsSectionRowIndexDexterityProficiency = 2;
const int kSavingThrowsSectionRowIndexDexterityAdvantage = 3;
const int kSavingThrowsSectionRowIndexConstitutionProficiency = 4;
const int kSavingThrowsSectionRowIndexConstitutionAdvantage = 5;
const int kSavingThrowsSectionRowIndexIntelligenceProficiency = 6;
const int kSavingThrowsSectionRowIndexIntelligenceAdvantage = 7;
const int kSavingThrowsSectionRowIndexWisdomProficiency = 8;
const int kSavingThrowsSectionRowIndexWisdomAdvantage = 9;
const int kSavingThrowsSectionRowIndexCharismaProficiency = 10;
const int kSavingThrowsSectionRowIndexCharismaAdvantage = 11;
NSString *const kIdentifierName = @"monster.name";
NSString *const kIdentifierSize = @"monster.size";
NSString *const kIdentifierType = @"monster.type";
NSString *const kIdentifierSubtype = @"monster.subtype";
NSString *const kIdentifierAlignment = @"monster.alignment";
NSString *const kIdentifierCustomHP = @"monster.customHPText";
NSString *const kIdentifierCustomSpeed = @"monster.customSpeed";
NSString *const kIdentifierCustomArmor = @"monster.customArmor";
NSString *const kIdentifierStrengthScore = @"monster.strengthScore";
NSString *const kIdentiferDexterityScore = @"monster.dexterityScore";
NSString *const kIdentifierConstitutionScore = @"monster.constitutionScore";
NSString *const kIdentifierIntelligenceScore = @"monster.intelligenceScore";
NSString *const kIdentifierWisdomScore = @"monster.wisdomScore";
NSString *const kIdentifierCharismaScore = @"monster.charismaScore";
NSString *const kIdentifierHitDice = @"monster.hitDice";
NSString *const kIdentifierBaseSpeed = @"monster.baseSpeed";
NSString *const kIdentifierBurrowSpeed = @"monster.burrowSpeed";
NSString *const kIdentifierClimbSpeed = @"monster.climbSpeed";
NSString *const kIdentifierFlySpeed = @"monster.flySpeed";
NSString *const kIdentifierSwimSpeed = @"monster.swimSpeed";
NSString *const kIdentifierNaturalArmorBonus = @"monster.naturalArmorBonus";
NSString *const kIdentifierHasCustomHP = @"monster.customHP";
NSString *const kIdentifierCanHover = @"monster.canHover";
NSString *const kIdentifierHasCustomSpeed = @"monster.hasCustomSpeed";
NSString *const kIdentifierHasShield = @"monster.hasShield";
NSString *const kIdentifierArmorType = @"monster.armorType";
NSString *const kIdentifierStrengthSavingThrowAdvantage = @"monster.savingThrows.strength.advantage";
NSString *const kIdentifierStrengthSavingThrowProficiency = @"monster.savingThrows.strength.proficiency";
NSString *const kIdentifierDexteritySavingThrowAdvantage = @"monster.savingThrows.dexterity.advantage";
NSString *const kIdentifierDexteritySavingThrowProficiency = @"monster.savingThrows.dexterity.proficiency";
NSString *const kIdentifierConstitutionSavingThrowAdvantage = @"monster.savingThrows.constitution.advantage";
NSString *const kIdentifierConstitutionSavingThrowProficiency = @"monster.savingThrows.constitution.proficiency";
NSString *const kIdentifierIntelligenceSavingThrowAdvantage = @"monster.savingThrows.intelligence.advantage";
NSString *const kIdentifierIntelligenceSavingThrowProficiency = @"monster.savingThrows.intelligence.proficiency";
NSString *const kIdentifierWisdomSavingThrowAdvantage = @"monster.savingThrows.wisdom.advantage";
NSString *const kIdentifierWisdomSavingThrowProficiency = @"monster.savingThrows.wisdom.proficiency";
NSString *const kIdentifierCharismaSavingThrowAdvantage = @"monster.savingThrows.charisma.advantage";
NSString *const kIdentifierCharismaSavingThrowProficiency = @"monster.savingThrows.charisma.proficiency";
@implementation EditMonsterViewController {
NSManagedObjectContext *_context;
NSArray<MCChoice*>* _armorTypes;
NSArray<MCChoice*>* _proficiencyTypes;
NSArray<MCChoice*>* _advantageTypes;
}
- (void)viewDidLoad {
[super viewDidLoad];
AppDelegate *appDelegate = (AppDelegate*)UIApplication.sharedApplication.delegate;
_context = appDelegate.persistentContainer.viewContext;
_armorTypes = [NSArray arrayWithObjects:
[MCChoice choiceWithLabel:NSLocalizedString(@"None", @"")
andValue:kArmorNameNone],
[MCChoice choiceWithLabel:NSLocalizedString(@"Natural Armor", @"")
andValue:kArmorNameNaturalArmor],
[MCChoice choiceWithLabel:NSLocalizedString(@"Mage Armor", @"")
andValue:kArmorNameMageArmor],
[MCChoice choiceWithLabel:NSLocalizedString(@"Padded", @"")
andValue:kArmorNamePadded],
[MCChoice choiceWithLabel:NSLocalizedString(@"Leather", @"")
andValue:kArmorNameLeather],
[MCChoice choiceWithLabel:NSLocalizedString(@"Studded", @"")
andValue:kArmorNameStuddedLeather],
[MCChoice choiceWithLabel:NSLocalizedString(@"Hide", @"")
andValue:kArmorNameHide],
[MCChoice choiceWithLabel:NSLocalizedString(@"Chain Shirt", @"")
andValue:kArmorNameChainShirt],
[MCChoice choiceWithLabel:NSLocalizedString(@"Scale Mail", @"")
andValue:kArmorNameScaleMail],
[MCChoice choiceWithLabel:NSLocalizedString(@"Breastplate", @"")
andValue:kArmorNameBreastplate],
[MCChoice choiceWithLabel:NSLocalizedString(@"Half Plate", @"")
andValue:kArmorNameHalfPlate],
[MCChoice choiceWithLabel:NSLocalizedString(@"Ring Mail", @"")
andValue:kArmorNameRingMail],
[MCChoice choiceWithLabel:NSLocalizedString(@"Chain Mail", @"")
andValue:kArmorNameChainMail],
[MCChoice choiceWithLabel:NSLocalizedString(@"Splint", @"")
andValue:kArmorNameSplintMail],
[MCChoice choiceWithLabel:NSLocalizedString(@"Plate", @"")
andValue:kArmorNamePlateMail],
[MCChoice choiceWithLabel:NSLocalizedString(@"Other", @"")
andValue:kArmorNameOther],
nil];
_proficiencyTypes = [NSArray arrayWithObjects:
[MCChoice choiceWithLabel:NSLocalizedString(@"None", @"")
andValue:kProficiencyTypeNone],
[MCChoice choiceWithLabel:NSLocalizedString(@"Proficient", @"")
andValue:kProficiencyTypeProficient],
[MCChoice choiceWithLabel:NSLocalizedString(@"Expertise", @"")
andValue:kProficiencyTypeExpertise],
nil];
_advantageTypes = [NSArray arrayWithObjects:
[MCChoice choiceWithLabel:NSLocalizedString(@"None", @"")
andValue:kAdvantageTypeNone],
[MCChoice choiceWithLabel:NSLocalizedString(@"Advantage", @"")
andValue:kAdvantageTypeAdvantage],
[MCChoice choiceWithLabel:NSLocalizedString(@"Disadvantage", @"")
andValue:kAdvantageTypeDisadvantage],
nil];
self.monsterTableView.allowsSelection = NO;
self.monsterTableView.allowsSelectionDuringEditing = NO;
self.monsterTableView.dataSource = self;
self.monsterTableView.delegate = self;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.editingMonster = [[Monster alloc] initWithMonster:self.originalMonster];
}
- (UITableViewCell*) makeSafeCell {
#if DEBUG
return nil;
#else
return [[UITableViewCell alloc] init];
#endif
}
- (MCShortStringFieldTableViewCell*) makeShortStringCellFromTableView:(UITableView*)tableView
withIdentifier:(NSString*)identifier
label:(NSString*)label
andInitialValue:(NSString*)initialValue {
MCShortStringFieldTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MCShortStringField"];
if (!cell || ![cell isKindOfClass:[MCShortStringFieldTableViewCell class]]) {
return nil;
}
cell.delegate = self;
cell.identifier = identifier;
cell.label = label;
cell.value = initialValue;
// TODO: move these to better properties on MCShortStringFieldTableViewCell they should be stored via label and initialValue/value.
cell.textField.text = initialValue;
cell.textField.placeholder = label;
return cell;
}
- (MCIntegerFieldTableViewCell*) makeIntegerCellFromTableView:(UITableView*)tableView
withIdentifier:(NSString*)identifier
label:(NSString*)label
andInitialValue:(int)initialValue {
MCIntegerFieldTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MCIntegerField"];
if (!cell || ![cell isKindOfClass:[MCIntegerFieldTableViewCell class]]) {
return nil;
}
cell.delegate = self;
cell.identifier = identifier;
cell.label = label;
cell.value = initialValue;
return cell;
}
- (MCBooleanFieldTableViewCell*) makeBooleanCellFromTableView:(UITableView*)tableView
withIdentifier:(NSString*)identifier
label:(NSString*)label
andInitialValue:(BOOL)initialValue {
MCBooleanFieldTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MCBooleanField"];
if (!cell || ![cell isKindOfClass:[MCBooleanFieldTableViewCell class]]) {
return nil;
}
cell.delegate = self;
cell.identifier = identifier;
cell.label = label;
cell.value = initialValue;
return cell;
}
- (MCSelectFieldTableViewCell*) makeSelectCellFromTableView:(UITableView*)tableView
withIdentifier:(NSString*)identifier
label:(NSString*)label
initialValue:(NSObject*)initialValue
andChoices:(NSArray*)choices {
MCSelectFieldTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MCSelectField"];
if (!cell || ![cell isKindOfClass:[MCSelectFieldTableViewCell class]]) {
return nil;
}
cell.delegate = self;
cell.identifier = identifier;
cell.label = label;
cell.selectedValue = initialValue;
cell.choices = choices;
return cell;
}
- (MCRadioFieldTableViewCell*) makeRadioCellFromTableView:(UITableView*)tableView
withIdentifier:(NSString*)identifier
label:(NSString*)label
initialValue:(NSObject*)initialValue
andChoices:(NSArray*)choices {
MCRadioFieldTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MCRadioField"];
if (!cell || ![cell isKindOfClass:[MCRadioFieldTableViewCell class]]) {
return nil;
}
cell.delegate = self;
cell.identifier = identifier;
cell.label = label;
// TODO: possibly swap these two
cell.selectedValue = initialValue;
cell.choices = choices;
return cell;
}
#pragma mark - Navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([@"DiscardChanges" isEqualToString:segue.identifier]) {
[_context rollback];
} else if ([@"SaveChanges" isEqualToString:segue.identifier]) {
// TODO: this should use a method on originalMonster to copy values from editingMonster or pass the new monster back some way. Core Data would save and probably trigger a refresh in the receiving view.
[self.originalMonster copyFromMonster:self.editingMonster];
[_context refreshObject:self.editingMonster mergeChanges:NO];
[_context save:nil];
} else {
NSLog(@"Unknown Segue %@", segue.identifier);
}
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section {
switch(section) {
case kSectionIndexBasicInfo:
// Section 0 is basic info
// * Name
// * Size
// * Type
// * Subtype
// * Alignment
return 8;
case kSectionIndexArmor:
return 4;
case kSectionIndexSpeed:
return 8;
case kSectionIndexAbilityScores:
return 6;
case kSectionIndexSavingThrows:
return 12; // 12
default:
return 0;
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 5;
}
- (NSString *)tableView:(UITableView *)tableView
titleForHeaderInSection:(NSInteger)section {
switch(section) {
case kSectionIndexBasicInfo:
return NSLocalizedString(@"Basic Info", @"Section title");
case kSectionIndexArmor:
return NSLocalizedString(@"Armor", @"Section title");
case kSectionIndexSpeed:
return NSLocalizedString(@"Speed", @"Section title");
case kSectionIndexAbilityScores:
return NSLocalizedString(@"Ability Scores", @"Section title");
case kSectionIndexSavingThrows:
return NSLocalizedString(@"Saving Throws", @"Section title");
default:
return nil;
}
}
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
UITableViewCell *newCell = nil;
switch (indexPath.section) {
case kSectionIndexBasicInfo:
switch (indexPath.row) {
case kBasicInfoSectionRowIndexName:
newCell = [self makeShortStringCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierName
label:NSLocalizedString(@"Name", @"Placeholder text for the name of a monster or NPC.")
andInitialValue:self.editingMonster.name];
break;
case kBasicInfoSectionRowIndexSize:
newCell = [self makeShortStringCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierSize
label:NSLocalizedString(@"Size", @"Placehodler text for the size of a monster or NPC.")
andInitialValue:self.editingMonster.size];
break;
case kBasicInfoSectionRowIndexType:
newCell = [self makeShortStringCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierType
label:NSLocalizedString(@"Type", @"Placehodler text for the type of a monster or NPC.")
andInitialValue:self.editingMonster.type];
break;
case kBasicInfoSectionRowIndexSubtype:
newCell = [self makeShortStringCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierSubtype
label:NSLocalizedString(@"Subtype", @"Placeholder text for the subtype of a monster or NPC.")
andInitialValue:self.editingMonster.subtype];
break;
case kBasicInfoSectionRowIndexAlignment:
newCell = [self makeShortStringCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierAlignment
label: NSLocalizedString(@"Alignment", @"Placeholder text for the alignment of a monster or NPC.")
andInitialValue:self.editingMonster.alignment];
break;
case kBasicInfoSectionRowIndexHitDice:
newCell = [self makeIntegerCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierHitDice
label:NSLocalizedString(@"Hit Dice", @"")
andInitialValue:self.editingMonster.hitDice];
break;
case kBasicInfoSectionRowIndexCustomHP:
newCell = [self makeBooleanCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierHasCustomHP
label:NSLocalizedString(@"Custom HP", @"")
andInitialValue:self.editingMonster.customHP];
break;
case kBasicInfoSectionRowIndexCustomHPText:
newCell = [self makeShortStringCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierCustomHP
label:NSLocalizedString(@"Custom HP Text", @"")
andInitialValue:self.editingMonster.hpText];
break;
}
break;
case kSectionIndexArmor:
switch (indexPath.row) {
case kArmorSectionRowIndexArmorType:
newCell = [self makeSelectCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierArmorType
label:NSLocalizedString(@"Type", @"")
initialValue:self.editingMonster.armorType
andChoices:_armorTypes];
break;
case kArmorSectionRowIndexHasShield:
newCell = [self makeBooleanCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierHasShield
label:NSLocalizedString(@"Shield", @"")
andInitialValue:self.editingMonster.hasShield];
break;
case kArmorSectionRowIndexCustomArmor:
newCell = [self makeShortStringCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierCustomArmor
label:NSLocalizedString(@"Custom Armor", @"")
andInitialValue:self.editingMonster.customArmor];
break;
case kArmorSectionRowIndexNaturalArmorBonus:
newCell = [self makeIntegerCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierNaturalArmorBonus
label:NSLocalizedString(@"Natural Armor Bonus", @"")
andInitialValue:self.editingMonster.naturalArmorBonus];
break;
}
break;
case kSectionIndexSpeed:
switch (indexPath.row) {
case kSpeedSectionRowIndexBaseSpeed:
newCell = [self makeIntegerCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierBaseSpeed
label:NSLocalizedString(@"Base", @"")
andInitialValue:self.editingMonster.baseSpeed];
break;
case kSpeedSectionRowIndexBurrowSpeed:
newCell = [self makeIntegerCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierBurrowSpeed
label:NSLocalizedString(@"Burrow", @"")
andInitialValue:self.editingMonster.burrowSpeed];
break;
case kSpeedSectionRowIndexClimbSpeed:
newCell = [self makeIntegerCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierClimbSpeed
label:NSLocalizedString(@"Climb", @"")
andInitialValue:self.editingMonster.climbSpeed];
break;
case kSpeedSectionRowIndexFlySpeed:
newCell = [self makeIntegerCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierFlySpeed
label:NSLocalizedString(@"Fly", @"")
andInitialValue:self.editingMonster.flySpeed];
break;
case kSpeedSectionRowIndexCanHover:
newCell = [self makeBooleanCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierCanHover
label:NSLocalizedString(@"Hover", @"")
andInitialValue:self.editingMonster.canHover];
break;
case kSpeedSectionRowIndexSwimSpeed:
newCell = [self makeIntegerCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierSwimSpeed
label:NSLocalizedString(@"Swim", @"")
andInitialValue:self.editingMonster.swimSpeed];
break;
case kSpeedSectionRowIndexHasCustomSpeed:
newCell = [self makeBooleanCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierHasCustomSpeed
label:NSLocalizedString(@"Custom Speed", @"")
andInitialValue:self.editingMonster.hasCustomSpeed];
break;
case kSpeedSectionRowIndexCustomSpeed:
newCell = [self makeShortStringCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierCustomSpeed
label:NSLocalizedString(@"Custom Speed", @"")
andInitialValue:self.editingMonster.customSpeed];
break;
}
break;
case kSectionIndexAbilityScores:
switch (indexPath.row) {
case kAbilityScoreSectionRowIndexStrength:
newCell = [self makeIntegerCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierStrengthScore
label:NSLocalizedString(@"STR", @"Placeholder abbreviation for the strength score of a monster or NPC.")
andInitialValue:self.editingMonster.strengthScore];
break;
case kAbilityScoreSectionRowIndexDexterity:
newCell = [self makeIntegerCellFromTableView:self.monsterTableView
withIdentifier:kIdentiferDexterityScore
label:NSLocalizedString(@"DEX", @"Placeholder abbreviation for the dexterity score of a monster or NPC.")
andInitialValue:self.editingMonster.dexterityScore];
break;
case kAbilityScoreSectionRowIndexConstitution:
newCell = [self makeIntegerCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierConstitutionScore
label:NSLocalizedString(@"CON", @"Placeholder abbreviation for the constitution score of a monster or NPC.")
andInitialValue:self.editingMonster.constitutionScore];
break;
case kAbilityScoreSectionRowIndexIntelligence:
newCell = [self makeIntegerCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierIntelligenceScore
label:NSLocalizedString(@"INT", @"Placeholder abbreviation for the intelligence score of a monster or NPC.")
andInitialValue:self.editingMonster.intelligenceScore];
break;
case kAbilityScoreSectionRowIndexWisdom:
newCell = [self makeIntegerCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierWisdomScore
label:NSLocalizedString(@"WIS", @"Placeholder abbreviation for the wisdom score of a monster or NPC.")
andInitialValue:self.editingMonster.wisdomScore];
break;
case kAbilityScoreSectionRowIndexCharisma:
newCell = [self makeIntegerCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierCharismaScore
label:NSLocalizedString(@"CHA", @"Placeholder abbreviation for the charisma score of a monster or NPC.")
andInitialValue:self.editingMonster.charismaScore];
break;
}
break;
case kSectionIndexSavingThrows:
switch (indexPath.row) {
case kSavingThrowsSectionRowIndexStrengthProficiency:
newCell = [self makeRadioCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierStrengthSavingThrowProficiency
label:NSLocalizedString(@"Strength Proficiency", @"")
initialValue:self.editingMonster.strengthSavingThrowProficiency
andChoices:_proficiencyTypes];
break;
case kSavingThrowsSectionRowIndexStrengthAdvantage:
newCell = [self makeRadioCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierStrengthSavingThrowAdvantage
label:NSLocalizedString(@"Strength Advantage", @"")
initialValue:self.editingMonster.strengthSavingThrowAdvantage
andChoices:_advantageTypes];
break;
case kSavingThrowsSectionRowIndexDexterityProficiency:
newCell = [self makeRadioCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierDexteritySavingThrowProficiency
label:NSLocalizedString(@"Dexterity Proficiency", @"")
initialValue:self.editingMonster.dexteritySavingThrowProficiency
andChoices:_proficiencyTypes];
break;
case kSavingThrowsSectionRowIndexDexterityAdvantage:
newCell = [self makeRadioCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierDexteritySavingThrowAdvantage
label:NSLocalizedString(@"Dexterity Advantage", @"")
initialValue:self.editingMonster.dexteritySavingThrowAdvantage
andChoices:_advantageTypes];
break;
case kSavingThrowsSectionRowIndexConstitutionProficiency:
newCell = [self makeRadioCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierConstitutionSavingThrowProficiency
label:NSLocalizedString(@"Constitution Proficiency", @"")
initialValue:self.editingMonster.constitutionSavingThrowProficiency
andChoices:_proficiencyTypes];
break;
case kSavingThrowsSectionRowIndexConstitutionAdvantage:
newCell = [self makeRadioCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierConstitutionSavingThrowAdvantage
label:NSLocalizedString(@"Constitution Advantage", @"")
initialValue:self.editingMonster.constitutionSavingThrowAdvantage
andChoices:_advantageTypes];
break;
case kSavingThrowsSectionRowIndexIntelligenceProficiency:
newCell = [self makeRadioCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierIntelligenceSavingThrowProficiency
label:NSLocalizedString(@"Intelligence Proficiency", @"")
initialValue:self.editingMonster.intelligenceSavingThrowProficiency
andChoices:_proficiencyTypes];
break;
case kSavingThrowsSectionRowIndexIntelligenceAdvantage:
newCell = [self makeRadioCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierIntelligenceSavingThrowAdvantage
label:NSLocalizedString(@"Intelligence Advantage", @"")
initialValue:self.editingMonster.intelligenceSavingThrowAdvantage
andChoices:_advantageTypes];
break;
case kSavingThrowsSectionRowIndexWisdomProficiency:
newCell = [self makeRadioCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierWisdomSavingThrowProficiency
label:NSLocalizedString(@"Wisdom Proficiency", @"")
initialValue:self.editingMonster.wisdomSavingThrowProficiency
andChoices:_proficiencyTypes];
break;
case kSavingThrowsSectionRowIndexWisdomAdvantage:
newCell = [self makeRadioCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierWisdomSavingThrowAdvantage
label:NSLocalizedString(@"Wisdom Advantage", @"")
initialValue:self.editingMonster.wisdomSavingThrowAdvantage
andChoices:_advantageTypes];
break;
case kSavingThrowsSectionRowIndexCharismaProficiency:
newCell = [self makeRadioCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierCharismaSavingThrowProficiency
label:NSLocalizedString(@"Charisma Proficiency", @"")
initialValue:self.editingMonster.charismaSavingThrowProficiency
andChoices:_proficiencyTypes];
break;
case kSavingThrowsSectionRowIndexCharismaAdvantage:
newCell = [self makeRadioCellFromTableView:self.monsterTableView
withIdentifier:kIdentifierCharismaSavingThrowAdvantage
label:NSLocalizedString(@"Charisma Advantage", @"")
initialValue:self.editingMonster.charismaSavingThrowAdvantage
andChoices:_advantageTypes];
break;
}
break;
}
if (!newCell) {
NSLog(@"ERROR: Unable to build a cell for %@", indexPath);
newCell = [self makeSafeCell];
}
return newCell;
}
#pragma mark - MCShortStringFieldDelegate
- (void)editableValueDidChange:(NSObject*)value forIdentifier:(NSString*)identifier andType:(NSString*)type {
if ([kMCFieldValueTypeString isEqualToString:type]) {
if ([kIdentifierName isEqualToString:identifier]) {
self.editingMonster.name = (NSString*)value;
} else if ([kIdentifierSize isEqualToString:identifier]) {
self.editingMonster.size = (NSString*)value;
} else if ([kIdentifierType isEqualToString:identifier]) {
self.editingMonster.type = (NSString*)value;
} else if ([kIdentifierSubtype isEqualToString:identifier]) {
self.editingMonster.subtype = (NSString*)value;
} else if ([kIdentifierAlignment isEqualToString:identifier]) {
self.editingMonster.alignment = (NSString*)value;
} else if ([kIdentifierCustomHP isEqualToString:identifier]) {
self.editingMonster.hpText = (NSString*)value;
} else if ([kIdentifierCustomSpeed isEqualToString:identifier]) {
self.editingMonster.customSpeed = (NSString*)value;
} else if ([kIdentifierCustomArmor isEqualToString:identifier]) {
self.editingMonster.customArmor = (NSString*)value;
}
}
if ([kMCFieldValueTypeInteger isEqualToString:type]) {
if ([kIdentifierStrengthScore isEqualToString:identifier]) {
self.editingMonster.strengthScore = [(NSNumber*)value intValue];
} else if ([kIdentiferDexterityScore isEqualToString:identifier]) {
self.editingMonster.dexterityScore = [(NSNumber*)value intValue];
} else if ([kIdentifierConstitutionScore isEqualToString:identifier]) {
self.editingMonster.constitutionScore = [(NSNumber*)value intValue];
} else if ([kIdentifierIntelligenceScore isEqualToString:identifier]) {
self.editingMonster.intelligenceScore = [(NSNumber*)value intValue];
} else if ([kIdentifierWisdomScore isEqualToString:identifier]) {
self.editingMonster.wisdomScore = [(NSNumber*)value intValue];
} else if ([kIdentifierCharismaScore isEqualToString:identifier]) {
self.editingMonster.charismaScore = [(NSNumber*)value intValue];
} else if ([kIdentifierHitDice isEqualToString:identifier]) {
self.editingMonster.hitDice = [(NSNumber*)value intValue];
} else if ([kIdentifierBaseSpeed isEqualToString:identifier]) {
self.editingMonster.baseSpeed = [(NSNumber*)value intValue];
} else if ([kIdentifierBurrowSpeed isEqualToString:identifier]) {
self.editingMonster.burrowSpeed = [(NSNumber*)value intValue];
} else if ([kIdentifierClimbSpeed isEqualToString:identifier]) {
self.editingMonster.climbSpeed = [(NSNumber*)value intValue];
} else if ([kIdentifierFlySpeed isEqualToString:identifier]) {
self.editingMonster.flySpeed = [(NSNumber*)value intValue];
} else if ([kIdentifierSwimSpeed isEqualToString:identifier]) {
self.editingMonster.swimSpeed = [(NSNumber*)value intValue];
} else if ([kIdentifierNaturalArmorBonus isEqualToString:identifier]) {
self.editingMonster.naturalArmorBonus = [(NSNumber*)value intValue];
}
}
if ([kMCFieldValueTypeBoolean isEqualToString:type]) {
if ([kIdentifierHasCustomHP isEqualToString:identifier]) {
self.editingMonster.customHP = [(NSNumber*)value boolValue];
} else if ([kIdentifierCanHover isEqualToString:identifier]) {
self.editingMonster.canHover = [(NSNumber*)value boolValue];
} else if ([kIdentifierHasCustomSpeed isEqualToString:identifier]) {
self.editingMonster.hasCustomSpeed = [(NSNumber*)value boolValue];
} else if ([kIdentifierHasShield isEqualToString:identifier]) {
self.editingMonster.hasShield = [(NSNumber*)value boolValue];
}
}
if ([kMCFieldValueTypeChoice isEqualToString:type]) {
if ([kIdentifierArmorType isEqualToString:identifier]) {
self.editingMonster.armorType = (NSString*)value;
} else if ([kIdentifierStrengthSavingThrowAdvantage isEqualToString:identifier]) {
self.editingMonster.strengthSavingThrowAdvantage = (NSString*)value;
} else if ([kIdentifierStrengthSavingThrowProficiency isEqualToString:identifier]) {
self.editingMonster.strengthSavingThrowProficiency = (NSString*)value;
} else if ([kIdentifierDexteritySavingThrowAdvantage isEqualToString:identifier]) {
self.editingMonster.dexteritySavingThrowAdvantage = (NSString*)value;
} else if ([kIdentifierDexteritySavingThrowProficiency isEqualToString:identifier]) {
self.editingMonster.dexteritySavingThrowProficiency = (NSString*)value;
} else if ([kIdentifierConstitutionSavingThrowAdvantage isEqualToString:identifier]) {
self.editingMonster.constitutionSavingThrowAdvantage = (NSString*)value;
} else if ([kIdentifierConstitutionSavingThrowProficiency isEqualToString:identifier]) {
self.editingMonster.constitutionSavingThrowProficiency = (NSString*)value;
} else if ([kIdentifierIntelligenceSavingThrowAdvantage isEqualToString:identifier]) {
self.editingMonster.intelligenceSavingThrowAdvantage = (NSString*)value;
} else if ([kIdentifierIntelligenceSavingThrowProficiency isEqualToString:identifier]) {
self.editingMonster.intelligenceSavingThrowProficiency = (NSString*)value;
} else if ([kIdentifierWisdomSavingThrowAdvantage isEqualToString:identifier]) {
self.editingMonster.wisdomSavingThrowAdvantage = (NSString*)value;
} else if ([kIdentifierWisdomSavingThrowProficiency isEqualToString:identifier]) {
self.editingMonster.wisdomSavingThrowProficiency = (NSString*)value;
} else if ([kIdentifierCharismaSavingThrowAdvantage isEqualToString:identifier]) {
self.editingMonster.charismaSavingThrowAdvantage = (NSString*)value;
} else if ([kIdentifierCharismaSavingThrowProficiency isEqualToString:identifier]) {
self.editingMonster.charismaSavingThrowProficiency = (NSString*)value;
}
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
@end

View File

@@ -1,26 +0,0 @@
//
// MCBooleanFieldTableViewCell.h
// MonsterCards
//
// Created by Tom Hicks on 9/25/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "MCFormFieldDelegate.h"
NS_ASSUME_NONNULL_BEGIN
@interface MCBooleanFieldTableViewCell : UITableViewCell
@property NSString* identifier;
@property NSString* label;
@property BOOL value;
@property (weak, nonatomic) id<MCFormFieldDelegate> delegate;
@property (weak, nonatomic) IBOutlet UILabel *labelView;
@property (weak, nonatomic) IBOutlet UISwitch *switchView;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,58 +0,0 @@
//
// MCBooleanFieldTableViewCell.m
// MonsterCards
//
// Created by Tom Hicks on 9/25/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "MCBooleanFieldTableViewCell.h"
@implementation MCBooleanFieldTableViewCell
@synthesize value = _value;
- (void)setValue:(BOOL)value {
if (value != _value) {
_value = value;
if (self.switchView) {
self.switchView.on = _value;
}
}
}
- (BOOL)value {
return _value;
}
@synthesize label = _label;
- (void)setLabel:(NSString*)label {
if (![_label isEqualToString:label]) {
_label = label;
if (self.labelView) {
self.labelView.text = label;
}
}
}
- (NSString*)label {
return _label;
}
- (void)awakeFromNib {
[super awakeFromNib];
self.switchView.on = self.value;
self.labelView.text = self.label;
}
- (IBAction)valueChanged:(id)sender {
self.value = self.switchView.on;
if (self.delegate != nil) {
[self.delegate editableValueDidChange:[NSNumber numberWithBool:self.value]
forIdentifier:self.identifier
andType:kMCFieldValueTypeBoolean];
}
}
@end

View File

@@ -1,28 +0,0 @@
//
// MCChoice.h
// MonsterCards
//
// Created by Tom Hicks on 9/26/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface MCChoice : NSObject
@property NSString* label;
@property NSObject* value;
+(id)choiceWithLabel:(NSString*)label
andValue:(NSObject*)value;
-(id)initWithLabel:(NSString*)label
andValue:(NSObject*)value;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,29 +0,0 @@
//
// MCChoice.m
// MonsterCards
//
// Created by Tom Hicks on 9/26/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "MCChoice.h"
@implementation MCChoice
+(id)choiceWithLabel:(NSString*)label
andValue:(NSObject*)value {
return [[MCChoice alloc] initWithLabel:label
andValue:value];
}
-(id)initWithLabel:(NSString*)label
andValue:(NSObject*)value {
self = [super init];
self.label = label;
self.value = value;
return self;
}
@end

View File

@@ -1,17 +0,0 @@
//
// MCFormFieldConstants.h
// MonsterCards
//
// Created by Tom Hicks on 9/17/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#ifndef MCFormFieldConstants_h
#define MCFormFieldConstants_h
extern NSString* const kMCFieldValueTypeInteger;
extern NSString* const kMCFieldValueTypeString;
extern NSString* const kMCFieldValueTypeBoolean;
extern NSString* const kMCFieldValueTypeChoice;
#endif /* MCFormFieldConstants_h */

View File

@@ -1,15 +0,0 @@
//
// MCFormFieldConstants.m
// MonsterCards
//
// Created by Tom Hicks on 9/17/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MCFormFieldConstants.h"
NSString* const kMCFieldValueTypeInteger = @"Integer";
NSString* const kMCFieldValueTypeString = @"String";
NSString* const kMCFieldValueTypeBoolean = @"Boolean";
NSString* const kMCFieldValueTypeChoice = @"Choice";

View File

@@ -1,22 +0,0 @@
//
// MCFormFieldDelegate.h
// MonsterCards
//
// Created by Tom Hicks on 9/9/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#ifndef MCFormFieldDelegate_h
#define MCFormFieldDelegate_h
#import "MCFormFieldConstants.h"
@protocol MCFormFieldDelegate <NSObject>
@optional
-(void)editableValueDidChange:(NSObject*)value forIdentifier:(NSString*)identifier andType:(NSString*)type;
@end
#endif /* MCFormFieldDelegate_h */

View File

@@ -1,28 +0,0 @@
//
// MCIntegerFieldTableViewCell.h
// MonsterCards
//
// Created by Tom Hicks on 9/17/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "MCFormFieldDelegate.h"
NS_ASSUME_NONNULL_BEGIN
@interface MCIntegerFieldTableViewCell : UITableViewCell <UITextFieldDelegate>
@property NSString* identifier;
@property NSString* label;
@property int value;
@property (weak, nonatomic) id<MCFormFieldDelegate> delegate;
@property (weak, nonatomic) IBOutlet UITextField *textField;
@property (weak, nonatomic) IBOutlet UIStepper *stepper;
@property (weak, nonatomic) IBOutlet UILabel *labelView;
- (IBAction)stepperValueChanged:(id)sender;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,72 +0,0 @@
//
// MCIntegerFieldTableViewCell.m
// MonsterCards
//
// Created by Tom Hicks on 9/17/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "MCIntegerFieldTableViewCell.h"
@implementation MCIntegerFieldTableViewCell
@synthesize value = _value;
-(void) setValue:(int)number {
if (_value != number) {
NSNumber *newValue = [NSNumber numberWithInt:number];
_value = number;
if (self.textField) {
self.textField.text = [newValue stringValue];
}
if (self.stepper) {
self.stepper.value = number;
}
if (self.delegate) {
[self.delegate editableValueDidChange:newValue
forIdentifier:self.identifier
andType:kMCFieldValueTypeInteger];
}
}
}
- (int) value {
return _value;
}
@synthesize label = _label;
- (void)setLabel:(NSString*)label {
if (![_label isEqualToString:label]) {
_label = label;
if (self.textField) {
self.textField.placeholder = label;
}
if (self.labelView) {
self.labelView.text = label;
}
}
}
- (NSString*)label {
return _label;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self.textField addTarget:self
action:@selector(textFieldValueChanged:)
forControlEvents:UIControlEventEditingChanged];
self.textField.text = [[NSNumber numberWithInt:_value] stringValue];
self.stepper.value = _value;
}
- (void)textFieldValueChanged:(UITextField*)textField {
self.value = [textField.text intValue];
}
- (IBAction)stepperValueChanged:(id)sender {
self.value = self.stepper.value;
}
@end

View File

@@ -1,29 +0,0 @@
//
// MCRadioFieldTableViewCell.h
// MonsterCards
//
// Created by Tom Hicks on 9/26/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "MCFormFieldDelegate.h"
#import "MCChoice.h"
NS_ASSUME_NONNULL_BEGIN
@interface MCRadioFieldTableViewCell : UITableViewCell
@property NSString* identifier;
@property NSString* label;
@property NSObject* selectedValue;
@property NSArray<MCChoice*>* choices;
@property (weak, nonatomic) id<MCFormFieldDelegate> delegate;
@property (weak, nonatomic) IBOutlet UILabel *labelView;
@property (weak, nonatomic) IBOutlet UISegmentedControl *segmentedControl;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,131 +0,0 @@
//
// MCRadioFieldTableViewCell.m
// MonsterCards
//
// Created by Tom Hicks on 9/26/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "MCRadioFieldTableViewCell.h"
@implementation MCRadioFieldTableViewCell {
MCChoice* _selectedChoice;
}
-(MCChoice*)findChoiceWithValue:(NSObject*)value
inArray:(NSArray*)array {
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id element, NSDictionary *bindings) {
if (![element isKindOfClass:[MCChoice class]]) {
return NO;
}
MCChoice *choice = (MCChoice*)element;
return [choice.value isEqual:value];
}];
NSArray<MCChoice*> *matchingChoices = [array filteredArrayUsingPredicate:predicate];
MCChoice *foundChoice = matchingChoices.count > 0 ? matchingChoices.firstObject : nil;
return foundChoice;
}
-(void)notifyChangedValue {
NSUInteger selectedIndex = [_choices indexOfObject:_selectedChoice];
[self.segmentedControl setSelectedSegmentIndex:selectedIndex];
if (_delegate) {
[_delegate editableValueDidChange:_selectedValue
forIdentifier:_identifier
andType:kMCFieldValueTypeChoice];
}
}
-(void)updateSegments {
if (_segmentedControl) {
[_segmentedControl removeAllSegments];
int index = 0;
for (MCChoice *choice in _choices) {
[_segmentedControl insertSegmentWithTitle:choice.label atIndex:index animated:NO];
index++;
}
_segmentedControl.selectedSegmentIndex = [_choices indexOfObject:_selectedChoice];
}
}
@synthesize choices = _choices;
-(void)setChoices:(NSArray<MCChoice*>*)choices {
MCChoice *foundChoice = [self findChoiceWithValue:_selectedValue
inArray:choices];
if ([_choices isEqualToArray:choices]) {
// Choices are equivalent so selected value. Pointer may have changed but content hasn't.
_selectedChoice = foundChoice;
} else if (foundChoice) {
// Choices are different but selected value is in the new choices. Pointer may have changed but content hasn't.
_selectedChoice = foundChoice;
} else {
// Choices are different and selected value is not in the new choices. Select the first choice or nil if there are none.
_selectedChoice = [choices firstObject];
}
_choices = choices;
if (_selectedValue != foundChoice.value) {
self.selectedValue = foundChoice.value;
}
[self updateSegments];
}
-(NSArray<MCChoice*>*)choices {
return _choices;
}
@synthesize label = _label;
-(void)setLabel:(NSString*)label {
if (![_label isEqualToString:label]) {
_label = label;
}
if (_labelView && ![_labelView.text isEqualToString:label]) {
_labelView.text = label;
}
}
-(NSString*)label {
return _label;
}
@synthesize selectedValue = _selectedValue;
-(void)setSelectedValue:(NSObject*)value {
NSObject *newValue = nil;
MCChoice *foundChoice = [self findChoiceWithValue:value inArray:_choices];
if (!_choices) {
newValue = value;
} else if (!foundChoice) {
newValue = nil;
} else {
newValue = foundChoice.value;
}
_selectedChoice = foundChoice;
if (_selectedValue != newValue) {
_selectedValue = newValue;
[self notifyChangedValue];
}
}
-(NSObject*)selectedValue {
return _selectedValue;
}
- (void)awakeFromNib {
[super awakeFromNib];
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (IBAction)selectedSegmentChanged:(id)sender {
NSInteger selectedIndex = _segmentedControl.selectedSegmentIndex;
MCChoice *newChoice = [_choices objectAtIndex:selectedIndex];
_selectedChoice = newChoice;
_selectedValue = _selectedChoice.value;
[self notifyChangedValue];
}
@end

View File

@@ -1,29 +0,0 @@
//
// MCSelectFieldTableViewCell.h
// MonsterCards
//
// Created by Tom Hicks on 9/26/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "MCFormFieldDelegate.h"
#import "MCChoice.h"
NS_ASSUME_NONNULL_BEGIN
@interface MCSelectFieldTableViewCell : UITableViewCell<UIPickerViewDataSource, UIPickerViewDelegate, UITextFieldDelegate>
@property NSString* identifier;
@property NSString* label;
@property NSObject* selectedValue;
@property NSArray<MCChoice*>* choices;
@property (weak, nonatomic) id<MCFormFieldDelegate> delegate;
@property (weak, nonatomic) IBOutlet UITextField *textField;
@property (weak, nonatomic) IBOutlet UILabel *labelView;
@property (nonatomic) UIPickerView *pickerView;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,182 +0,0 @@
//
// MCSelectFieldTableViewCell.m
// MonsterCards
//
// Created by Tom Hicks on 9/26/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "MCSelectFieldTableViewCell.h"
@implementation MCSelectFieldTableViewCell {
MCChoice* _selectedChoice;
}
-(MCChoice*)findChoiceWithValue:(NSObject*)value
inArray:(NSArray*)array {
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id element, NSDictionary *bindings) {
if (![element isKindOfClass:[MCChoice class]]) {
return NO;
}
MCChoice *choice = (MCChoice*)element;
return [choice.value isEqual:value];
}];
NSArray<MCChoice*> *matchingChoices = [array filteredArrayUsingPredicate:predicate];
MCChoice *foundChoice = matchingChoices.count > 0 ? matchingChoices.firstObject : nil;
return foundChoice;
}
-(void)notifyChangedValue {
[self updateView];
if (_delegate) {
[_delegate editableValueDidChange:_selectedValue
forIdentifier:_identifier
andType:kMCFieldValueTypeChoice];
}
}
-(void)updateView {
self.textField.text = _selectedChoice.label;
if (_choices && _choices.count > 0) {
NSInteger selectedRow = [_choices indexOfObject:_selectedChoice];
if (selectedRow != NSNotFound) {
[self.pickerView selectRow:selectedRow inComponent:0 animated:YES];
} else {
[self.pickerView selectRow:0 inComponent:0 animated:YES];
}
}
}
@synthesize choices = _choices;
-(void)setChoices:(NSArray<MCChoice*>*)choices {
MCChoice *foundChoice = [self findChoiceWithValue:_selectedValue
inArray:choices];
if ([_choices isEqualToArray:choices]) {
// Choices are equivalent so selected value. Pointer may have changed but content hasn't.
_selectedChoice = foundChoice;
} else if (foundChoice) {
// Choices are different but selected value is in the new choices. Pointer may have changed but content hasn't.
_selectedChoice = foundChoice;
} else {
// Choices are different and selected value is not in the new choices. Select the first choice or nil if there are none.
_selectedChoice = [choices firstObject];
}
_choices = choices;
if (_selectedValue != foundChoice.value) {
self.selectedValue = foundChoice.value;
}
[self updateView];
}
-(NSArray<MCChoice*>*)choices {
return _choices;
}
@synthesize label = _label;
-(void)setLabel:(NSString*)label {
if (![_label isEqualToString:label]) {
_label = label;
}
if (_labelView && ![_labelView.text isEqualToString:label]) {
_labelView.text = label;
}
}
-(NSString*)label {
return _label;
}
@synthesize selectedValue = _selectedValue;
-(void)setSelectedValue:(NSObject*)value {
NSObject *newValue = nil;
MCChoice *foundChoice = [self findChoiceWithValue:value inArray:_choices];
if (!_choices) {
newValue = value;
} else if (!foundChoice) {
foundChoice = [_choices firstObject];
newValue = foundChoice.value;
} else {
newValue = foundChoice.value;
}
_selectedChoice = foundChoice;
if (_selectedValue != newValue) {
_selectedValue = newValue;
[self notifyChangedValue];
}
}
-(NSObject*)selectedValue {
return _selectedValue;
}
- (void)awakeFromNib {
[super awakeFromNib];
self.pickerView = [[UIPickerView alloc] init];
self.pickerView.delegate = self;
self.pickerView.dataSource = self;
self.textField.inputView = self.pickerView;
self.pickerView.translatesAutoresizingMaskIntoConstraints = NO;
UIToolbar *toolbar = [[UIToolbar alloc] init];
[toolbar sizeToFit];
UIBarButtonItem *button =
[[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Done", @"Button label")
style:UIBarButtonItemStylePlain
target:self
action:@selector(choiceSelected)];
[toolbar setItems:@[button]
animated:true];
[toolbar setUserInteractionEnabled:YES];
self.textField.inputAccessoryView = toolbar;
self.textField.hidden = NO;
self.textField.text = _selectedChoice.label;
self.textField.delegate = self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (void)choiceSelected {
[self endEditing:true];
}
#pragma mark - UIPickerViewDataSource
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component {
return [_choices count];
}
#pragma mark - UIPickerViewDelegate
- (NSString *)pickerView:(UIPickerView *)pickerView
titleForRow:(NSInteger)row
forComponent:(NSInteger)component {
return [_choices objectAtIndex:row].label;
}
- (void)pickerView:(UIPickerView *)pickerView
didSelectRow:(NSInteger)row
inComponent:(NSInteger)component {
_selectedChoice = [_choices objectAtIndex:row];
self.textField.text = _selectedChoice.label;
self.selectedValue = _selectedChoice.value;
}
#pragma mark - UITextFieldDelegate
- (void)textFieldDidBeginEditing:(UITextField *)textField {
[self updateView];
}
@end

View File

@@ -1,25 +0,0 @@
//
// EditableShortStringTableViewCell.h
// MonsterCards
//
// Created by Tom Hicks on 9/9/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "MCFormFieldDelegate.h"
NS_ASSUME_NONNULL_BEGIN
@interface MCShortStringFieldTableViewCell : UITableViewCell
@property NSString* identifier;
@property NSString* label;
@property NSString* value;
@property (weak, nonatomic) id<MCFormFieldDelegate> delegate;
@property (weak, nonatomic) IBOutlet UITextField *textField;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,57 +0,0 @@
//
// EditableShortStringTableViewCell.m
// MonsterCards
//
// Created by Tom Hicks on 9/9/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "MCShortStringFieldTableViewCell.h"
@implementation MCShortStringFieldTableViewCell
@synthesize value = _value;
- (void)setValue:(NSString*)value {
if (![_value isEqualToString:value]) {
_value = value;
if (self.textField) {
self.textField.text = value;
}
}
}
- (NSString*)value {
return _value;
}
@synthesize label = _label;
- (void)setLabel:(NSString*)label {
if (![_label isEqualToString:label]) {
_label = label;
if (self.textField) {
self.textField.placeholder = label;
}
}
}
- (NSString*)label {
return _label;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self.textField addTarget:self action:@selector(valueChanged:) forControlEvents:UIControlEventEditingChanged];
}
- (void)valueChanged:(UITextField*)textField {
NSString *newValue = textField.text;
if (self.delegate != nil) {
[self.delegate editableValueDidChange:newValue
forIdentifier:self.identifier
andType:kMCFieldValueTypeString];
}
}
@end

View File

@@ -0,0 +1,60 @@
//
// Library.swift
// MonsterCards
//
// Created by Tom Hicks on 1/15/21.
//
import SwiftUI
struct Library: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [
NSSortDescriptor(keyPath: \Monster.name, ascending: true),
],
animation: .default)
var allMonsters: FetchedResults<Monster>
var body: some View {
NavigationView{
List(allMonsters) { monster in
NavigationLink(destination: MonsterDetail(monster: monster)) {
Text(monster.name ?? "")
}
}
.navigationTitle("Library")
.navigationBarTitleDisplayMode(.inline)
.toolbar(content: {
ToolbarItem(placement: .primaryAction) {
Button(action: addMonster) {
Image(systemName:"plus")
}
}
})
}
}
private func addMonster() {
withAnimation {
let newItem = Monster(context: viewContext)
newItem.name = "Unnamed Monster"
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}
struct Library_Previews: PreviewProvider {
static var previews: some View {
return Library().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}

View File

@@ -1,19 +0,0 @@
//
// LibraryViewController.h
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface LibraryViewController : UITableViewController <UITableViewDelegate, UITableViewDataSource>
@property (strong, nonatomic) IBOutlet UITableView *monstersTable;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,94 +0,0 @@
//
// LibraryViewController.m
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "LibraryViewController.h"
#import "Monster.h"
#import "MonsterViewController.h"
#import "AppDelegate.h"
@interface LibraryViewController ()
@property NSArray* allMonsters;
@end
@implementation LibraryViewController {
NSManagedObjectContext *_context;
}
- (void)viewDidLoad {
[super viewDidLoad];
AppDelegate *appDelegate = (AppDelegate*)UIApplication.sharedApplication.delegate;
_context = appDelegate.persistentContainer.viewContext;
}
- (void)viewWillAppear:(BOOL)animated {
self.allMonsters = [_context executeFetchRequest:[Monster fetchRequest] error:nil];
[self.monstersTable reloadData];
}
- (IBAction)addNewMonster:(id)sender {
Monster *monster = [[Monster alloc] initWithContext:_context];
monster.name = NSLocalizedString(@"Unnamed Monster", @"The default name of a new monster.");
self.allMonsters = [self.allMonsters arrayByAddingObject:monster];
//DispatchQueue.main.async{"code here"}
[_context save:nil];
[self.monstersTable reloadData];
}
#pragma mark - Navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([@"ShowMonsterDetail" isEqualToString:segue.identifier]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
if ([segue.destinationViewController isKindOfClass:[MonsterViewController class]]) {
MonsterViewController *vc = (MonsterViewController*)segue.destinationViewController;
vc.monster = [self.allMonsters objectAtIndex:indexPath.row];
}
}
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section {
return [self.allMonsters count];
}
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
static NSString *simpleTableIdentifier = @"MonsterCell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
Monster *monster = (Monster*)[self.allMonsters objectAtIndex:indexPath.row];
cell.textLabel.text = monster.name;
return cell;
}
#pragma mark - UITableViewDelegate
- (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView
trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath {
UIContextualAction *action = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:NSLocalizedString(@"Delete", @"Command to delete an object.") handler:^(UIContextualAction *action, __kindof UIView *sourceView, void (^completionHandler)(BOOL actionPerformed)) {
Monster *monster = [self.allMonsters objectAtIndex:indexPath.row];
[self->_context deleteObject:monster];
[self->_context save:nil];
self.allMonsters = [self->_context executeFetchRequest:[Monster fetchRequest] error:nil];
[self.tableView reloadData];
}];
UISwipeActionsConfiguration *config = [UISwipeActionsConfiguration configurationWithActions:[NSArray arrayWithObject:action]];
return config;
}
@end

View File

@@ -0,0 +1,34 @@
//
// MCAdvantagePicker.swift
// MonsterCards
//
// Created by Tom Hicks on 1/17/21.
//
import SwiftUI
struct MCAdvantagePicker: View {
var label: String = ""
var value: Binding<AdvantageType>
var body: some View {
VStack(alignment: .leading) {
Text(label)
.font(.caption2)
Picker(
selection: value,
label: Text(label)) {
ForEach(AdvantageType.allCases) { advType in
Text(advType.displayName).tag(advType)
}
}
.pickerStyle(SegmentedPickerStyle())
}
}
}
struct MCAdvantagePicker_Previews: PreviewProvider {
static var previews: some View {
MCAdvantagePicker(value: .constant(AdvantageType.none))
}
}

View File

@@ -0,0 +1,34 @@
//
// MCProficiencyPicker.swift
// MonsterCards
//
// Created by Tom Hicks on 1/17/21.
//
import SwiftUI
struct MCProficiencyPicker: View {
var label: String = ""
var value: Binding<ProficiencyType>
var body: some View {
VStack(alignment: .leading) {
Text(label)
.font(.caption2)
Picker(
selection: value,
label: Text(label)) {
ForEach(ProficiencyType.allCases) { profType in
Text(profType.displayName).tag(profType)
}
}
.pickerStyle(SegmentedPickerStyle())
}
}
}
struct MCProficiencyPicker_Previews: PreviewProvider {
static var previews: some View {
MCProficiencyPicker(value: .constant(ProficiencyType.none))
}
}

View File

@@ -0,0 +1,37 @@
//
// MCStepperField.swift
// MonsterCards
//
// Created by Tom Hicks on 1/16/21.
//
import SwiftUI
struct MCStepperField: View {
var label: String = ""
var prefix: String = ""
var step: Int = 1
var suffix: String = ""
var value: Binding<Int64>
var body: some View {
VStack(alignment: .leading) {
Text(label)
.font(.caption2)
Stepper(
value: value,
step: step
) {
Text("\(prefix)\(value.wrappedValue)\(suffix)" as String)
}
}
}
}
struct MCStepperField_Previews: PreviewProvider {
static var previews: some View {
MCStepperField(
label: "Hit Dice",
value: .constant(4))
}
}

View File

@@ -0,0 +1,28 @@
//
// MCTextField.swift
// MonsterCards
//
// Created by Tom Hicks on 1/16/21.
//
import SwiftUI
struct MCTextField: View {
var label: String
var value: Binding<String>
var body: some View {
VStack(alignment: .leading) {
Text(label)
.font(.caption2)
TextField(label, text: value)
.autocapitalization(/*@START_MENU_TOKEN@*/.none/*@END_MENU_TOKEN@*/)
// .padding(.top, -4)
}
}
}
struct MCTextField_Previews: PreviewProvider {
static var previews: some View {
MCTextField(label: "Name", value: .constant("Ted"))
}
}

View File

@@ -0,0 +1,177 @@
//
// MonsterDetail.swift
// MonsterCards
//
// Created by Tom Hicks on 1/15/21.
//
import SwiftUI
struct LabeledField<Content: View>: View {
let content: Content
let label: String
@inlinable public init(
_ label: String,
@ViewBuilder content: () -> Content) {
self.content = content()
self.label = label
}
var body: some View {
HStack(alignment: .top) {
Text(label)
.fontWeight(.bold)
content
}
}
}
struct SectionDivider: View {
var body: some View {
Image("section-divider") // divider
.resizable()
.scaledToFit()
.padding(.vertical, 4)
}
}
struct SmallAbilityScore: View {
var label: String
var score: Int64
var modifier: Int
public init(
_ label: String,
_ score: Int64,
_ modifier: Int) {
self.label = label
self.score = score
self.modifier = modifier
}
var body: some View {
VStack {
Text(label)
.fontWeight(.bold)
Text(String(format: "%d (%+d)", score, modifier))
}
.frame(maxWidth: .infinity)
}
}
struct MonsterDetail: View {
let kTextColor: Color = Color(hex: 0x982818)
var monster: Monster
var body: some View {
ScrollView {
VStack (alignment: .leading) {
// meta: "(large humanoid (elf) lawful evil"
Text(monster.meta)
.font(.subheadline)
.foregroundColor(.secondary)
SectionDivider()
// AC
LabeledField("Armor Class") {
Text(monster.armorClassDescription)// armor class
}
// HP
LabeledField("Hit Points") {
Text(monster.hitPoints) // hit points
}
// Speed
LabeledField("Speed") {
Text(monster.speed) // speed
}
SectionDivider()
// Ability Scores
HStack {
SmallAbilityScore("STR", monster.strengthScore, monster.strengthModifier)
SmallAbilityScore("DEX", monster.dexterityScore, monster.dexterityModifier)
SmallAbilityScore("CON", monster.constitutionScore, monster.constitutionModifier)
SmallAbilityScore("INT", monster.intelligenceScore, monster.intelligenceModifier)
SmallAbilityScore("WIS", monster.wisdomScore, monster.wisdomModifier)
SmallAbilityScore("CHA", monster.charismaScore, monster.charismaModifier)
}
SectionDivider()
let savingThrowsDescription = monster.savingThrowsDescription
if (!savingThrowsDescription.isEmpty) {
LabeledField("Saving Throws") {
Text(monster.savingThrowsDescription)
}
}
}
.padding(.horizontal)
.foregroundColor(kTextColor)
}
.toolbar(content: {
ToolbarItem(placement: .primaryAction) {
NavigationLink("Edit", destination: EditMonster(monster: monster))
}
})
.navigationTitle(monster.name ?? "")
.navigationBarTitleDisplayMode(.inline)
}
private func editMonster() {
print("Edit Monster pressed")
}
}
struct MonsterDetail_Previews: PreviewProvider {
static var previews: some View {
let context = PersistenceController.preview.container.viewContext
let monster = Monster.init(context: context)
monster.name = "Steve"
monster.size = "Medium"
monster.type = "humanoid"
monster.subtype = "human"
monster.alignment = "LG"
monster.hitDice = 6
monster.hasCustomHP = true
monster.customHP = "12 (1d10)+2"
monster.baseSpeed = 5
monster.burrowSpeed = 10
monster.climbSpeed = 15
monster.flySpeed = 20
monster.swimSpeed = 25
monster.canHover = true
monster.hasCustomSpeed = false
monster.customSpeed = "walk: 5 ft."
monster.strengthScore = 8
monster.dexterityScore = 10
monster.constitutionScore = 12
monster.intelligenceScore = 14
monster.wisdomScore = 16
monster.charismaScore = 18
monster.strengthSavingThrowAdvantageEnum = AdvantageType.none
monster.strengthSavingThrowProficiencyEnum = ProficiencyType.none
monster.dexteritySavingThrowAdvantageEnum = AdvantageType.advantage
monster.dexteritySavingThrowProficiencyEnum = ProficiencyType.proficient
monster.constitutionSavingThrowAdvantageEnum = AdvantageType.disadvantage
monster.constitutionSavingThrowProficiencyEnum = ProficiencyType.expertise
monster.intelligenceSavingThrowAdvantageEnum = AdvantageType.none
monster.intelligenceSavingThrowProficiencyEnum = ProficiencyType.expertise
monster.wisdomSavingThrowAdvantageEnum = AdvantageType.advantage
monster.wisdomSavingThrowProficiencyEnum = ProficiencyType.proficient
monster.charismaSavingThrowAdvantageEnum = AdvantageType.disadvantage
monster.charismaSavingThrowProficiencyEnum = ProficiencyType.none
return Group {
MonsterDetail(monster: monster)
.environment(\.managedObjectContext, context)
.previewDevice("iPod touch (7th generation)")
MonsterDetail(monster: monster)
.environment(\.managedObjectContext, context)
.previewDevice("iPad Pro (11-inch) (2nd generation)")
}
}
}

View File

@@ -1,33 +0,0 @@
//
// MonsterViewController.h
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "Monster.h"
NS_ASSUME_NONNULL_BEGIN
@interface MonsterViewController : UIViewController
@property (weak, nonatomic) IBOutlet UILabel *monsterName;
@property (weak, nonatomic) IBOutlet UILabel *monsterMeta;
@property (weak, nonatomic) IBOutlet UILabel *monsterArmorClass;
@property (weak, nonatomic) IBOutlet UILabel *monsterHitPoints;
@property (weak, nonatomic) IBOutlet UILabel *monsterSpeed;
@property (weak, nonatomic) IBOutlet UILabel *monsterStrength;
@property (weak, nonatomic) IBOutlet UILabel *monsterDexterity;
@property (weak, nonatomic) IBOutlet UILabel *monsterConstitution;
@property (weak, nonatomic) IBOutlet UILabel *monsterIntelligence;
@property (weak, nonatomic) IBOutlet UILabel *monsterWisdom;
@property (weak, nonatomic) IBOutlet UILabel *monsterCharisma;
@property (weak, nonatomic) IBOutlet UILabel *monsterSavingThrows;
@property Monster* monster;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,134 +0,0 @@
//
// MonsterViewController.m
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "MonsterViewController.h"
#import "EditMonsterViewController.h"
#import "HTMLHelper.h"
#import "AppDelegate.h"
@interface MonsterViewController ()
@end
NSString *const defaultFontFamily = @"helvetica";
NSString *const defaultFontSize = @"12pt";
NSString *const defaultTextColor = @"#9B2818";
NSString* makeHTMLFragmentString(NSString* format, ...) {
va_list args;
va_start(args, format);
NSString *childString = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
NSString *formattedString = [NSString stringWithFormat:@"<span style=\"font-family: %@; font-size: %@; color: %@;\">%@</span>", defaultFontFamily, defaultFontSize, defaultTextColor, childString];
return formattedString;
}
@implementation MonsterViewController {
NSManagedObjectContext *_context;
}
- (void)viewDidLoad {
[super viewDidLoad];
AppDelegate *appDelegate = (AppDelegate*)UIApplication.sharedApplication.delegate;
_context = appDelegate.persistentContainer.viewContext;
}
- (void)viewWillAppear:(BOOL)animated {
[_context refreshObject:self.monster mergeChanges:NO];
if (self.monsterName != nil) {
self.monsterName.text = self.monster.name;
} else if (self.navigationItem != nil) {
self.navigationItem.title = self.monster.name;
}
if (self.monsterName != nil) {
self.monsterName.text = self.monster.name;
} else if (self.navigationItem != nil) {
if (self.monster.name == nil) {
self.navigationItem.title = @"Unnamed Monster";
} else {
self.navigationItem.title = self.monster.name;
}
}
if (self.monsterMeta != nil) {
NSString *metaText = self.monster.meta;
if (metaText == nil) {
self.monsterMeta.text = @"";
} else {
self.monsterMeta.text = metaText;
}
}
if (self.monsterArmorClass != nil) {
NSString *armorClassDescription = self.monster.armorClassDescription;
if (armorClassDescription == nil) {
self.monsterArmorClass.text = @"";
} else {
self.monsterArmorClass.attributedText = [HTMLHelper attributedStringFromHTML:makeHTMLFragmentString(@"<b>Armor Class</b> %@</span>", armorClassDescription)];
}
}
if (self.monsterHitPoints != nil) {
NSString *hitPointsDescription = self.monster.hitPointsDescription;
if (hitPointsDescription == nil) {
self.monsterHitPoints.text = @"";
} else {
self.monsterHitPoints.attributedText = [HTMLHelper attributedStringFromHTML:makeHTMLFragmentString(@"<b>Hit Points</b> %@", hitPointsDescription)];
}
}
if (self.monsterSpeed != nil) {
NSString *speedDescription = self.monster.speedDescription;
if (speedDescription == nil) {
self.monsterSpeed.text = @"";
} else {
self.monsterSpeed.attributedText = [HTMLHelper attributedStringFromHTML:makeHTMLFragmentString(@"<b>Speed</b> %@", speedDescription)];
}
}
if (self.monsterStrength) {
self.monsterStrength.text = self.monster.strengthDescription;
}
if (self.monsterDexterity) {
self.monsterDexterity.text = self.monster.dexterityDescription;
}
if (self.monsterConstitution) {
self.monsterConstitution.text = self.monster.constitutionDescription;
}
if (self.monsterIntelligence) {
self.monsterIntelligence.text = self.monster.intelligenceDescription;
}
if (self.monsterWisdom) {
self.monsterWisdom.text = self.monster.wisdomDescription;
}
if (self.monsterCharisma) {
self.monsterCharisma.text = self.monster.charismaDescription;
}
if (self.monsterSavingThrows) {
NSString *savingThrowsDescription = self.monster.savingThrowsDescription;
if (savingThrowsDescription == nil) {
self.monsterSavingThrows.text = @"";
} else {
self.monsterSavingThrows.attributedText = [HTMLHelper attributedStringFromHTML:makeHTMLFragmentString(@"<b>Saving Throws</b> %@", savingThrowsDescription)];
}
}
}
- (IBAction)unwindWithSegue:(UIStoryboardSegue *)unwindSegue {
// UIViewController *sourceViewController = unwindSegue.sourceViewController;
// Use data from the view controller which initiated the unwind segue
}
#pragma mark - Navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([@"EditMonster" isEqualToString:segue.identifier]) {
if ([segue.destinationViewController isKindOfClass:[EditMonsterViewController class]]) {
EditMonsterViewController *vc = (EditMonsterViewController*)segue.destinationViewController;
vc.originalMonster = self.monster;
}
}
}
@end

View File

@@ -0,0 +1,41 @@
//
// Search.swift
// MonsterCards
//
// Created by Tom Hicks on 1/15/21.
//
import SwiftUI
struct Search: View {
@State private var searchText = ""
@Environment(\.managedObjectContext) var managedObjectContext
@FetchRequest(
sortDescriptors: [
NSSortDescriptor(keyPath: \Monster.name, ascending: true),
],
animation: .default)
var allMonsters: FetchedResults<Monster>
var body: some View {
NavigationView {
List {
SearchBar(text: $searchText)
.padding(.top, -30)
ForEach(allMonsters.filter({searchText.isEmpty ? true : $0.name?.containsCaseInsensitive(searchText) ?? false })) { monster in
NavigationLink(destination: MonsterDetail(monster: monster)) {
Text(monster.name ?? "")
}
}
}
}
}
}
struct Search_Previews: PreviewProvider {
static var previews: some View {
Search().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}

View File

@@ -0,0 +1,63 @@
//
// SearchBar.swift
// MonsterCards
//
// Created by Tom Hicks on 1/15/21.
//
import Foundation
import UIKit
import SwiftUI
struct SearchBar: UIViewRepresentable {
@Binding var text: String
class Coordinator: NSObject, UISearchBarDelegate {
@Binding var text: String
init(text: Binding<String>) {
_text = text
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
text = searchText
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
UIApplication.shared.endEditing()
}
}
func makeCoordinator() -> SearchBar.Coordinator {
return Coordinator(text: $text)
}
func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
let searchBar = UISearchBar(frame: .zero)
searchBar.delegate = context.coordinator
searchBar.autocapitalizationType = .none
return searchBar
}
func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) {
uiView.text = text
}
}
extension String {
func containsCaseInsensitive(_ string: String) -> Bool {
return self.localizedCaseInsensitiveContains(string)
}
}
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
struct SearchBar_Previews: PreviewProvider {
static var previews: some View {
SearchBar(text: .constant(""))
}
}

View File

@@ -1,20 +0,0 @@
//
// SearchViewController.h
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface SearchViewController : UITableViewController <UISearchBarDelegate, UITableViewDelegate, UITableViewDataSource>
@property (weak, nonatomic) IBOutlet UISearchBar *searchBar;
@property (weak, nonatomic) IBOutlet UITableView *searchResults;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,105 +0,0 @@
//
// SearchViewController.m
// MonsterCards
//
// Created by Tom Hicks on 9/4/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import "SearchViewController.h"
#import "MonsterViewController.h"
#import "Monster.h"
#import "AppDelegate.h"
@interface SearchViewController ()
@property NSArray* allMonsters;
@property NSArray* foundMonsters;
@end
@implementation SearchViewController {
NSManagedObjectContext *_context;
}
- (void)viewDidLoad {
[super viewDidLoad];
AppDelegate *appDelegate = (AppDelegate*)UIApplication.sharedApplication.delegate;
_context = appDelegate.persistentContainer.viewContext;
}
- (void)viewWillAppear:(BOOL)animated {
NSString *searchText = nil;
if (self.searchBar != nil) {
searchText = self.searchBar.text;
}
self.allMonsters = [_context executeFetchRequest:[Monster fetchRequest] error:nil];
self.foundMonsters = [self filterAllMonstersWithText:searchText];
[self.tableView reloadData];
}
#pragma mark - Navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([@"ShowMonsterDetail" isEqualToString:segue.identifier]) {
NSIndexPath *indexPath = [self.searchResults indexPathForSelectedRow];
if ([segue.destinationViewController isKindOfClass:[MonsterViewController class]]) {
MonsterViewController *vc = (MonsterViewController*)segue.destinationViewController;
vc.monster = [self.foundMonsters objectAtIndex:indexPath.row];
}
}
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section {
return [self.foundMonsters count];
}
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
static NSString *simpleTableIdentifier = @"MonsterCell";
UITableViewCell *cell = [self.searchResults dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
Monster *monster = (Monster*)[self.foundMonsters objectAtIndex:indexPath.row];
cell.textLabel.text = monster.name;
return cell;
}
#pragma mark - UISearchBarDelegate
- (NSArray*)filterAllMonstersWithText:(NSString *)searchText {
if (searchText != nil && ![@"" isEqualToString:searchText]) {
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id item, NSDictionary *bindings) {
if (![item isKindOfClass:[Monster class]]) {
return false;
}
Monster *monster = (Monster*)item;
if ([monster.name localizedCaseInsensitiveContainsString:searchText]) {
return true;
}
return false;
}];
return [self.allMonsters filteredArrayUsingPredicate:predicate];
} else {
return self.allMonsters;
}
}
- (void)searchBar:(UISearchBar *)searchBar
textDidChange:(NSString *)searchText {
self.foundMonsters = [self filterAllMonstersWithText:searchText];
[self.tableView reloadData];
}
@end

View File

@@ -1,19 +0,0 @@
//
// main.m
// MonsterCards
//
// Created by Tom Hicks on 9/2/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

View File

@@ -1,697 +0,0 @@
//
// JSONHelperTests.m
// MonsterCardsTests
//
// Created by Tom Hicks on 9/15/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <XCTest/XCTest.h>
#import "JSONHelper.h"
@interface JSONHelperTests : XCTestCase
@end
@implementation JSONHelperTests {
NSString *_jsonStringKey;
NSString *_jsonStringValue;
NSString *_jsonStringFragment;
NSString *_jsonIntegerKey;
NSNumber *_jsonIntegerValue;
NSString *_jsonIntegerFragment;
NSString *_jsonBooleanKey;
BOOL _jsonBooleanValue;
NSString *_jsonBooleanFragment;
NSString *_jsonDictionaryKey;
NSDictionary *_jsonDictionaryValue;
NSString *_jsonDictionaryFragment;
NSString *_jsonDictionaryStringValue;
NSString *_jsonArrayKey;
NSArray *_jsonArrayValue;
NSString *_jsonArrayFragment;
NSString *_jsonArrayStringValue;
}
NSString* escapeStringForJSON(NSString *unescaped) {
return [[unescaped stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"] stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
}
NSDictionary* readJSONDictionaryFromString(NSString *jsonString) {
NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *jsonRoot = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil];
if (![jsonRoot isKindOfClass:[NSDictionary class]]) {
return nil;
} else {
return jsonRoot;
}
}
NSArray* readJSONArrayFromString(NSString *jsonString) {
NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSArray *jsonRoot = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil];
if (![jsonRoot isKindOfClass:[NSArray class]]) {
return nil;
} else {
return jsonRoot;
}
}
- (void)setUp {
_jsonStringKey = @"my_string";
_jsonStringValue = @"Hello, World!";
_jsonStringFragment = [NSString stringWithFormat:@"\"%@\":\"%@\"", escapeStringForJSON(_jsonStringKey), escapeStringForJSON(_jsonStringValue)];
_jsonIntegerKey = @"my_int";
_jsonIntegerValue = @12345;
_jsonIntegerFragment = [NSString stringWithFormat:@"\"%@\":%@", escapeStringForJSON(_jsonIntegerKey), [_jsonIntegerValue stringValue]];
_jsonBooleanKey = @"my_bool";
_jsonBooleanValue = YES;
_jsonBooleanFragment = [NSString stringWithFormat:@"\"%@\":true", escapeStringForJSON(_jsonBooleanKey)];
_jsonDictionaryKey = @"my_dictionary";
_jsonDictionaryValue = @{_jsonStringKey: _jsonStringValue};
_jsonDictionaryStringValue = [NSString stringWithFormat:@"{%@}", _jsonStringFragment];
_jsonDictionaryFragment = [NSString stringWithFormat:@"\"%@\":%@", escapeStringForJSON(_jsonDictionaryKey), _jsonDictionaryStringValue];
_jsonArrayKey = @"my_array";
_jsonArrayValue = @[_jsonStringValue];
_jsonArrayStringValue = [NSString stringWithFormat:@"[\"%@\"]", _jsonStringValue];
_jsonArrayFragment = [NSString stringWithFormat:@"\"%@\":%@", escapeStringForJSON(_jsonArrayKey), _jsonArrayStringValue];
}
- (void)tearDown {
}
#pragma mark - Strings in Dictionaries
- (void)testReadStringFromDictionaryReturnsNilIfKeyNotPresent {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonIntegerFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSString *readString = [JSONHelper readStringFromDictionary:jsonRoot forKey:_jsonStringKey];
XCTAssertNil(readString);
}
- (void)testReadStringFromDictionaryWithDefaultReturnsDefaultIfKeyNotPresent {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonIntegerFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSString *readString = [JSONHelper readStringFromDictionary:jsonRoot forKey:_jsonStringKey withDefaultValue:_jsonStringValue];
XCTAssertEqualObjects(_jsonStringValue, readString);
}
- (void) testReadStringFromDictionaryReturnsCorrectValue {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonStringFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSString *readString = [JSONHelper readStringFromDictionary:jsonRoot forKey:_jsonStringKey];
XCTAssertEqualObjects(_jsonStringValue, readString);
}
- (void)testReadStringFromDictionaryWithDefaultReturnsCorrectValue {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonStringFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSString *readString = [JSONHelper readStringFromDictionary:jsonRoot forKey:_jsonStringKey withDefaultValue:@"Some other string"];
XCTAssertEqualObjects(_jsonStringValue, readString);
}
- (void) testReadStringFromDictionaryReturnsNilIfWrongType {
NSString *jsonString = [NSString stringWithFormat:@"{\"%@\":%@}", _jsonStringKey, _jsonIntegerValue];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSString *readString = [JSONHelper readStringFromDictionary:jsonRoot forKey:_jsonStringKey];
XCTAssertNil(readString);
}
#pragma mark - Strings in Arrays
- (void)testReadStringFromArrayReturnsNilIfNotAString {
NSString *jsonString = [NSString stringWithFormat:@"[%@]", _jsonIntegerValue];
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSString *readString = [JSONHelper readStringFromArray:jsonRoot forIndex:0];
XCTAssertNil(readString);
}
- (void)testReadStringFromArrayWithDefaultReturnsDefaultValueIfNotAString {
NSString *jsonString = [NSString stringWithFormat:@"[%@]", _jsonIntegerValue];
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSString *readString = [JSONHelper readStringFromArray:jsonRoot forIndex:0 withDefaultValue:_jsonStringValue];
XCTAssertEqualObjects(_jsonStringValue, readString);
}
- (void)testReadStringFromArrayThrowsIfIndexOutOfRange {
// TODO: Decide if this should throw or return nil
NSString *jsonString = @"[]";
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
XCTAssertThrows([JSONHelper readStringFromArray:jsonRoot forIndex:0]);
XCTAssertThrows([JSONHelper readStringFromArray:jsonRoot forIndex:-1]);
}
- (void)testReadStringFromArrayReturnsCorrectValue {
NSString *jsonString = [NSString stringWithFormat:@"[\"%@\"]", _jsonStringValue];
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSString *readString = [JSONHelper readStringFromArray:jsonRoot forIndex:0];
XCTAssertEqualObjects(_jsonStringValue, readString);
}
#pragma mark - Integers in Dictionaries
- (void)testReadIntegerFromDictionaryReturnsNilIfKeyNotPresent {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonStringFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSNumber *readNumber = [JSONHelper readNumberFromDictionary:jsonRoot forKey:_jsonIntegerKey];
XCTAssertNil(readNumber);
}
- (void)testReadIntegerFromDictionaryWithDefaultReturnsDefaultIfKeyNotPresent {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonStringFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSNumber *readNumber = [JSONHelper readNumberFromDictionary:jsonRoot forKey:_jsonIntegerKey withDefaultValue:_jsonIntegerValue];
XCTAssertEqualObjects(_jsonIntegerValue, readNumber);
}
- (void) testReadIntegerFromDictionaryReturnsCorrectValue {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonIntegerFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSNumber *readNumber = [JSONHelper readNumberFromDictionary:jsonRoot forKey:_jsonIntegerKey];
XCTAssertEqualObjects(_jsonIntegerValue, readNumber);
}
- (void)testReadIntegerFromDictionaryWithDefaultReturnsCorrectValue {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonIntegerFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSNumber *readNumber = [JSONHelper readNumberFromDictionary:jsonRoot forKey:_jsonIntegerKey withDefaultValue:@67890];
XCTAssertEqualObjects(_jsonIntegerValue, readNumber);
}
- (void) testReadIntegerFromDictionaryReturnsNilIfWrongType {
NSString *jsonString = [NSString stringWithFormat:@"{\"%@\":\"%@\"}", _jsonIntegerKey, _jsonStringValue];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSNumber *readNumber = [JSONHelper readNumberFromDictionary:jsonRoot forKey:_jsonIntegerKey];
XCTAssertNil(readNumber);
}
- (void)testReadIntFromDictionaryReturnsZeroIfKeyNotPresent {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonStringFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
int readNumber = [JSONHelper readIntFromDictionary:jsonRoot forKey:_jsonIntegerKey];
XCTAssertEqual(0, readNumber);
}
- (void)testReadIntFromDictionaryWithDefaultReturnsDefaultIfKeyNotPresent {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonStringFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
int readNumber = [JSONHelper readIntFromDictionary:jsonRoot forKey:_jsonIntegerKey withDefaultValue:[_jsonIntegerValue intValue]];
XCTAssertEqual([_jsonIntegerValue intValue], readNumber);
}
- (void) testReadIntFromDictionaryReturnsCorrectValue {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonIntegerFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
int readNumber = [JSONHelper readIntFromDictionary:jsonRoot forKey:_jsonIntegerKey];
XCTAssertEqual([_jsonIntegerValue intValue], readNumber);
}
- (void)testReadIntFromDictionaryWithDefaultReturnsCorrectValue {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonIntegerFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
int readNumber = [JSONHelper readIntFromDictionary:jsonRoot forKey:_jsonIntegerKey withDefaultValue:67890];
XCTAssertEqual([_jsonIntegerValue intValue], readNumber);
}
- (void) testReadIntFromDictionaryReturnsZeroIfWrongType {
NSString *jsonString = [NSString stringWithFormat:@"{\"%@\":\"%@\"}", _jsonIntegerKey, _jsonStringValue];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
int readNumber = [JSONHelper readIntFromDictionary:jsonRoot forKey:_jsonIntegerKey];
XCTAssertEqual(0, readNumber);
}
#pragma mark - Dictionaries in Dictionaries
- (void)testReadDictionaryFromDictionaryReturnsNilIfKeyNotPresent {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonStringFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSDictionary *readValue = [JSONHelper readDictionaryFromDictionary:jsonRoot forKey:_jsonDictionaryKey];
XCTAssertNil(readValue);
}
- (void)testReadDictionaryFromDictionaryWithDefaultReturnsDefaultIfKeyNotPresent{
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonStringFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSDictionary *readValue = [JSONHelper readDictionaryFromDictionary:jsonRoot forKey:_jsonDictionaryKey withDefaultValue:@{}];
XCTAssertEqualObjects(@{}, readValue);
}
- (void)testReadDictionaryFromDictionaryReturnsCorrectValue {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonDictionaryFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSDictionary *readValue = [JSONHelper readDictionaryFromDictionary:jsonRoot forKey:_jsonDictionaryKey];
XCTAssertEqualObjects(_jsonDictionaryValue, readValue);
}
- (void)testReadDictionaryFromDictionaryWithDefaultReturnsCorrectValue {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonIntegerFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSDictionary *readValue = [JSONHelper readDictionaryFromDictionary:jsonRoot forKey:_jsonDictionaryKey withDefaultValue:@{}];
XCTAssertEqualObjects(@{}, readValue);
}
- (void)testReadDictionaryFromDictionaryReturnsNilIfWrongType {
NSString *jsonString = [NSString stringWithFormat:@"{\"%@\":\"%@\"}", _jsonDictionaryKey, _jsonStringValue];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSDictionary *readValue = [JSONHelper readDictionaryFromDictionary:jsonRoot forKey:_jsonDictionaryKey];
XCTAssertNil(readValue);
}
#pragma mark - Arrays in Dictionaries
- (void)testReadArrayFromDictionaryReturnsNilIfKeyNotPresent {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonStringFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSArray *readValue = [JSONHelper readArrayFromDictionary:jsonRoot forKey:_jsonArrayKey];
XCTAssertNil(readValue);
}
- (void)testReadArrayFromDictionaryWithDefaultReturnsDefaultIfKeyNotPresent{
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonStringFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSArray *readValue = [JSONHelper readArrayFromDictionary:jsonRoot forKey:_jsonArrayKey withDefaultValue:@[]];
XCTAssertEqualObjects(@[], readValue);
}
- (void)testReadArrayFromDictionaryReturnsCorrectValue {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonArrayFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSArray *readValue = [JSONHelper readArrayFromDictionary:jsonRoot forKey:_jsonArrayKey];
XCTAssertEqualObjects(_jsonArrayValue, readValue);
}
- (void)testReadArrayFromDictionaryWithDefaultReturnsCorrectValue {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonIntegerFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSArray *readValue = [JSONHelper readArrayFromDictionary:jsonRoot forKey:_jsonArrayKey withDefaultValue:@[]];
XCTAssertEqualObjects(@[], readValue);
}
- (void)testReadArrayFromDictionaryReturnsNilIfWrongType {
NSString *jsonString = [NSString stringWithFormat:@"{\"%@\":\"%@\"}", _jsonArrayKey, _jsonStringValue];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSArray *readValue = [JSONHelper readArrayFromDictionary:jsonRoot forKey:_jsonArrayKey];
XCTAssertNil(readValue);
}
#pragma mark - Integers in Arrays
- (void)testReadIntegerFromArrayReturnsNilIfNotAnInteger {
NSString *jsonString = [NSString stringWithFormat:@"[\"%@\"]", _jsonStringValue];
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSNumber *readNumber = [JSONHelper readNumberFromArray:jsonRoot forIndex:0];
XCTAssertNil(readNumber);
}
- (void)testReadIntegerFromArrayWithDefaultReturnsDefaultValueIfNotAnInteger {
NSString *jsonString = [NSString stringWithFormat:@"[\"%@\"]", _jsonStringValue];
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSNumber *readNumber = [JSONHelper readNumberFromArray:jsonRoot forIndex:0 withDefaultValue:_jsonIntegerValue];
XCTAssertEqualObjects(_jsonIntegerValue, readNumber);
}
- (void)testReadIntegerFromArrayThrowsIfIndexOutOfRange {
// TODO: Decide if this should throw or return nil
NSString *jsonString = @"[]";
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
XCTAssertThrows([JSONHelper readNumberFromArray:jsonRoot forIndex:0]);
XCTAssertThrows([JSONHelper readNumberFromArray:jsonRoot forIndex:-1]);
}
- (void)testReadIntegerFromArrayReturnsCorrectValue {
NSString *jsonString = [NSString stringWithFormat:@"[%@]", _jsonIntegerValue];
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSNumber *readNumber = [JSONHelper readNumberFromArray:jsonRoot forIndex:0];
XCTAssertEqualObjects(_jsonIntegerValue, readNumber);
}
- (void)testReadIntFromArrayReturnsNilIfNotAnInteger {
NSString *jsonString = [NSString stringWithFormat:@"[\"%@\"]", _jsonStringValue];
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
int readNumber = [JSONHelper readIntFromArray:jsonRoot forIndex:0];
XCTAssertEqual(0, readNumber);
}
- (void)testReadIntFromArrayWithDefaultReturnsDefaultValueIfNotAnInteger {
NSString *jsonString = [NSString stringWithFormat:@"[\"%@\"]", _jsonStringValue];
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
int readNumber = [JSONHelper readIntFromArray:jsonRoot forIndex:0 withDefaultValue:[_jsonIntegerValue intValue]];
XCTAssertEqual([_jsonIntegerValue intValue], readNumber);
}
- (void)testReadIntFromArrayThrowsIfIndexOutOfRange {
// TODO: Decide if this should throw or return 0
NSString *jsonString = @"[]";
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
XCTAssertThrows([JSONHelper readIntFromArray:jsonRoot forIndex:0]);
XCTAssertThrows([JSONHelper readIntFromArray:jsonRoot forIndex:-1]);
}
- (void)testReadIntFromArrayReturnsCorrectValue {
NSString *jsonString = [NSString stringWithFormat:@"[%@]", _jsonIntegerValue];
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
int readNumber = [JSONHelper readIntFromArray:jsonRoot forIndex:0];
XCTAssertEqual([_jsonIntegerValue intValue], readNumber);
}
#pragma mark - BOOLs in Dictionaries
- (void)testReadBoolFromDictionaryReturnsFalseIfKeyNotPresent {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonStringFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
BOOL readValue = [JSONHelper readBoolFromDictionary:jsonRoot forKey:_jsonBooleanKey];
XCTAssertEqual(0, readValue);
}
- (void)testReadBoolFromDictionaryWithDefaultReturnsDefaultIfKeyNotPresent {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonStringFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
BOOL readValue = [JSONHelper readBoolFromDictionary:jsonRoot forKey:_jsonIntegerKey withDefaultValue:_jsonBooleanValue];
XCTAssertEqual(_jsonBooleanValue, readValue);
}
- (void) testReadBoolFromDictionaryReturnsCorrectValue {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonBooleanFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
BOOL readValue = [JSONHelper readBoolFromDictionary:jsonRoot forKey:_jsonBooleanKey];
XCTAssertEqual(_jsonBooleanValue, readValue);
}
- (void)testReadBoolFromDictionaryWithDefaultReturnsCorrectValue {
NSString *jsonString = [NSString stringWithFormat:@"{%@}", _jsonBooleanFragment];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
BOOL readValue = [JSONHelper readBoolFromDictionary:jsonRoot forKey:_jsonBooleanKey withDefaultValue:NO];
XCTAssertEqual(_jsonBooleanValue, readValue);
}
- (void) testReadBoolFromDictionaryReturnsFalseIfWrongType {
NSString *jsonString = [NSString stringWithFormat:@"{\"%@\":\"%@\"}", _jsonIntegerKey, _jsonStringValue];
NSDictionary *jsonRoot = readJSONDictionaryFromString(jsonString);
XCTAssertNotNil(jsonRoot);
BOOL readValue = [JSONHelper readBoolFromDictionary:jsonRoot forKey:_jsonIntegerKey];
XCTAssertEqual(NO, readValue);
}
#pragma mark - BOOLs in Arrays
- (void)testReadBoolFromArrayReturnsFalseIfNotCoercable {
NSString *jsonString = [NSString stringWithFormat:@"[\"%@\"]", _jsonStringValue];
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
BOOL readValue = [JSONHelper readBoolFromArray:jsonRoot forIndex:0];
XCTAssertEqual(NO, readValue);
}
- (void)testReadBoolFromArrayWithDefaultReturnsDefaultValueIfNotCoercable {
NSString *jsonString = [NSString stringWithFormat:@"[\"%@\"]", _jsonStringValue];
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
BOOL readValue = [JSONHelper readIntFromArray:jsonRoot forIndex:0 withDefaultValue:YES];
XCTAssertEqual(YES, readValue);
}
- (void)testReadBoolFromArrayThrowsIfIndexOutOfRange {
// TODO: Decide if this should throw or return 0
NSString *jsonString = @"[]";
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
XCTAssertThrows([JSONHelper readBoolFromArray:jsonRoot forIndex:0]);
XCTAssertThrows([JSONHelper readBoolFromArray:jsonRoot forIndex:-1]);
}
- (void)testReadBoolFromArrayReturnsCorrectValue {
NSString *jsonString = [NSString stringWithFormat:@"[%s]", _jsonBooleanValue ? "true" : "false"];
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
BOOL readValue = [JSONHelper readBoolFromArray:jsonRoot forIndex:0];
XCTAssertEqual(_jsonBooleanValue, readValue);
}
#pragma mark - Dictionaries in Arrays
- (void)testReadDictionaryFromArrayReturnsNilIfNotCoercable {
NSString *jsonString = [NSString stringWithFormat:@"[\"%@\"]", _jsonStringValue];
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSDictionary *readValue = [JSONHelper readDictionaryFromArray:jsonRoot forIndex:0];
XCTAssertNil(readValue);
}
- (void)testReadDictionaryFromArrayWithDefaultReturnsDefaultValueIfNotCoercable {
NSString *jsonString = [NSString stringWithFormat:@"[\"%@\"]", _jsonStringValue];
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSDictionary *readValue = [JSONHelper readDictionaryFromArray:jsonRoot forIndex:0 withDefaultValue:@{}];
XCTAssertEqualObjects(@{}, readValue);
}
- (void)testReadDictionaryFromArrayThrowsIfIndexOutOfRange {
// TODO: Decide if this should throw or return nil
NSString *jsonString = @"[]";
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
XCTAssertThrows([JSONHelper readDictionaryFromArray:jsonRoot forIndex:0]);
XCTAssertThrows([JSONHelper readDictionaryFromArray:jsonRoot forIndex:-1]);
}
- (void)testReadDictionaryFromArrayReturnsCorrectValue {
NSString *jsonString = [NSString stringWithFormat:@"[%@]", _jsonDictionaryStringValue];
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSDictionary *readValue = [JSONHelper readDictionaryFromArray:jsonRoot forIndex:0];
XCTAssertEqualObjects(_jsonDictionaryValue, readValue);
}
#pragma mark - Arrays in Arrays
- (void)testReadArrayFromArrayReturnsNilIfNotCoercable {
NSString *jsonString = [NSString stringWithFormat:@"[\"%@\"]", _jsonStringValue];
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSArray *readValue = [JSONHelper readArrayFromArray:jsonRoot forIndex:0];
XCTAssertNil(readValue);
}
- (void)testReadArrayFromArrayWithDefaultReturnsDefaultValueIfNotCoercable {
NSString *jsonString = [NSString stringWithFormat:@"[\"%@\"]", _jsonStringValue];
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSArray *readValue = [JSONHelper readArrayFromArray:jsonRoot forIndex:0 withDefaultValue:@[]];
XCTAssertEqualObjects(@[], readValue);
}
- (void)testReadArrayFromArrayThrowsIfIndexOutOfRange {
// TODO: Decide if this should throw or return nil
NSString *jsonString = @"[]";
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
XCTAssertThrows([JSONHelper readArrayFromArray:jsonRoot forIndex:0]);
XCTAssertThrows([JSONHelper readArrayFromArray:jsonRoot forIndex:-1]);
}
- (void)testReadArrayFromArrayReturnsCorrectValue {
NSString *jsonString = [NSString stringWithFormat:@"[%@]", _jsonArrayStringValue];
NSArray *jsonRoot = readJSONArrayFromString(jsonString);
XCTAssertNotNil(jsonRoot);
NSArray *readValue = [JSONHelper readArrayFromArray:jsonRoot forIndex:0];
XCTAssertEqualObjects(_jsonArrayValue, readValue);
}
#pragma mark - JSON parsing
- (void)testParseJSONDataReturnsDictionary {
NSString *jsonString = @"{\"a\":1,\"b\":2}";
NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
id parsedResult = [JSONHelper parseJSONData:jsonData];
XCTAssertNotNil(parsedResult);
XCTAssertTrue([parsedResult isKindOfClass:[NSDictionary class]]);
}
- (void)testParseJSONDataReturnsArray {
NSString *jsonString = @"[\"a\",1,\"b\",2]";
NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
id parsedResult = [JSONHelper parseJSONData:jsonData];
XCTAssertNotNil(parsedResult);
XCTAssertTrue([parsedResult isKindOfClass:[NSArray class]]);
}
- (void)testParseJSONDataAsDictionaryReturnsDictionary {
NSString *jsonString = @"{\"a\":1,\"b\":2}";
NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *parsedResult = [JSONHelper parseJSONDataAsDictionary:jsonData];
XCTAssertNotNil(parsedResult);
XCTAssertTrue([parsedResult isKindOfClass:[NSDictionary class]]);
}
- (void)testParseJSONDataAsDictionaryReturnsNilIfNotDictionary {
NSString *jsonString = @"[\"a\",1,\"b\",2]";
NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
id parsedResult = [JSONHelper parseJSONDataAsDictionary:jsonData];
XCTAssertNil(parsedResult);
}
- (void)testParseJSONDataAsArrayReturnsArray {
NSString *jsonString = @"[\"a\",1,\"b\",2]";
NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
id parsedResult = [JSONHelper parseJSONDataAsArray:jsonData];
XCTAssertNotNil(parsedResult);
XCTAssertTrue([parsedResult isKindOfClass:[NSArray class]]);
}
- (void)testParseJSONDataAsArrayReturnsNilIfNotArray {
NSString *jsonString = @"{\"a\":1,\"b\":2}";
NSData* jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
id parsedResult = [JSONHelper parseJSONDataAsArray:jsonData];
XCTAssertNil(parsedResult);
}
- (void)testParseJSONStringReturnsDictionary {
NSString *jsonString = @"{\"a\":1,\"b\":2}";
id parsedResult = [JSONHelper parseJSONString:jsonString];
XCTAssertNotNil(parsedResult);
XCTAssertTrue([parsedResult isKindOfClass:[NSDictionary class]]);
}
- (void)testParseJSONStringReturnsArray {
NSString *jsonString = @"[\"a\",1,\"b\",2]";
id parsedResult = [JSONHelper parseJSONString:jsonString];
XCTAssertNotNil(parsedResult);
XCTAssertTrue([parsedResult isKindOfClass:[NSArray class]]);
}
- (void)testParseJSONStringAsDictionaryReturnsDictionary {
NSString *jsonString = @"{\"a\":1,\"b\":2}";
NSDictionary *parsedResult = [JSONHelper parseJSONStringAsDictionary:jsonString];
XCTAssertNotNil(parsedResult);
XCTAssertTrue([parsedResult isKindOfClass:[NSDictionary class]]);
}
- (void)testParseJSONStringAsDictionaryReturnsNilIfNotDictionary {
NSString *jsonString = @"[\"a\",1,\"b\",2]";
id parsedResult = [JSONHelper parseJSONStringAsDictionary:jsonString];
XCTAssertNil(parsedResult);
}
- (void)testParseJSONStringAsArrayReturnsArray {
NSString *jsonString = @"[\"a\",1,\"b\",2]";
id parsedResult = [JSONHelper parseJSONStringAsArray:jsonString];
XCTAssertNotNil(parsedResult);
XCTAssertTrue([parsedResult isKindOfClass:[NSArray class]]);
}
- (void)testParseJSONStringAsArrayReturnsNilIfNotArray {
NSString *jsonString = @"{\"a\":1,\"b\":2}";
id parsedResult = [JSONHelper parseJSONStringAsArray:jsonString];
XCTAssertNil(parsedResult);
}
@end

View File

@@ -1,54 +0,0 @@
//
// AbilityTests.m
// MonsterCardsTests
//
// Created by Tom Hicks on 9/5/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <XCTest/XCTest.h>
#import "Ability.h"
@interface AbilityTests : XCTestCase
@end
@implementation AbilityTests {
Ability *_ability;
NSString *_name;
NSString *_description;
}
- (void)setUp {
_ability = [[Ability alloc] init];
_name = @"My Ability Name";
_description = @"This is my ability description.";
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
- (void)testDefaultInitializer {
XCTAssertEqualObjects(@"", _ability.name);
XCTAssertEqualObjects(@"", _ability.abilityDescription);
}
- (void)testNameGetterAndSetter {
_ability.name = _name;
XCTAssertEqualObjects(_name, _ability.name);
}
- (void)testDescriptionGetterAndSetter {
_ability.abilityDescription = _description;
XCTAssertEqualObjects(_description, _ability.abilityDescription);
}
- (void)testInitWithNameAndDescription {
_ability = [[Ability alloc] initWithName:_name andDescription:_description];
XCTAssertEqualObjects(_name, _ability.name);
XCTAssertEqualObjects(_description, _ability.abilityDescription);
}
@end

View File

@@ -1,54 +0,0 @@
//
// ActionTests.m
// MonsterCardsTests
//
// Created by Tom Hicks on 9/5/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <XCTest/XCTest.h>
#import "Action.h"
@interface ActionTests : XCTestCase
@end
@implementation ActionTests {
Action *_action;
NSString *_name;
NSString *_description;
}
- (void)setUp {
_action = [[Action alloc] init];
_name = @"My Action Name";
_description = @"This is my action description.";
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
- (void)testDefaultInitializer {
XCTAssertEqualObjects(@"", _action.name);
XCTAssertEqualObjects(@"", _action.actionDescription);
}
- (void)testNameGetterAndSetter {
_action.name = _name;
XCTAssertEqualObjects(_name, _action.name);
}
- (void)testDescriptionGetterAndSetter {
_action.actionDescription = _description;
XCTAssertEqualObjects(_description, _action.actionDescription);
}
- (void)testInitWithNameAndDescription {
_action = [[Action alloc] initWithName:_name andDescription:_description];
XCTAssertEqualObjects(_name, _action.name);
XCTAssertEqualObjects(_description, _action.actionDescription);
}
@end

View File

@@ -1,61 +0,0 @@
//
// DamageTypeTests.m
// MonsterCardsTests
//
// Created by Tom Hicks on 9/5/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <XCTest/XCTest.h>
#import "DamageType.h"
@interface DamageTypeTests : XCTestCase {
DamageType *_damageType;
NSString *_name;
NSString *_note;
NSString *_type;
}
@end
@implementation DamageTypeTests
- (void)setUp {
_damageType = [[DamageType alloc] init];
_name = @"My Damage Type";
_note = @"A note";
_type = @"A type";
}
- (void)tearDown {}
- (void)testDefaultInitializer {
XCTAssertEqualObjects(@"", _damageType.name);
XCTAssertEqualObjects(@"", _damageType.note);
XCTAssertEqualObjects(@"", _damageType.type);
}
- (void)testInitWithNameNoteAndType {
_damageType = [[DamageType alloc] initWithName:_name note:_note andType:_type];
XCTAssertEqualObjects(_name, _damageType.name);
XCTAssertEqualObjects(_note, _damageType.note);
XCTAssertEqualObjects(_type, _damageType.type);
}
- (void)testNameGetterAndSetter {
_damageType.name = _name;
XCTAssertEqualObjects(_name, _damageType.name);
}
- (void)testNoteGetterAndSetter {
_damageType.note = _note;
XCTAssertEqualObjects(_note, _damageType.note);
}
- (void)testTypeGetterAndSetter {
_damageType.type = _type;
XCTAssertEqualObjects(_type, _damageType.type);
}
@end

View File

@@ -1,56 +0,0 @@
//
// Language.m
// MonsterCardsTests
//
// Created by Tom Hicks on 9/5/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <XCTest/XCTest.h>
#import "Language.h"
@interface LanguageTests : XCTestCase
@end
@implementation LanguageTests {
Language *_language;
NSString *_name;
BOOL _canSpeak;
}
- (void)setUp {
_language = [[Language alloc] init];
_name = @"English";
_canSpeak = YES;
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
- (void)testDefaultInitializer {
XCTAssertNotNil(_language);
XCTAssertEqualObjects(@"", _language.name);
XCTAssertEqual(YES, _language.speaks);
}
- (void)testInitWithNameAndSpeaks {
_language = [[Language alloc] initWithName:_name andSpeaks:_canSpeak];
XCTAssertNotNil(_language);
XCTAssertEqualObjects(_name, _language.name);
XCTAssertEqual(_canSpeak, _language.speaks);
}
- (void)testNameGetterAndSetter {
_language.name = _name;
XCTAssertEqualObjects(_name, _language.name);
}
- (void)testSpeaksGetterAndSetter {
_language.speaks = NO;
XCTAssertEqual(NO, _language.speaks);
}
@end

View File

@@ -1,438 +0,0 @@
//
// MonsterTests.m
// MonsterCardsTests
//
// Created by Tom Hicks on 9/5/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <XCTest/XCTest.h>
@import OCHamcrest;
@import OCMockito;
#import "Monster.h"
@interface MonsterTests : XCTestCase
@end
@implementation MonsterTests {
Monster *_monster;
NSManagedObjectContext *_context;
NSString *_jsonString;
NSData *_jsonData;
}
- (void)setUp {
_context = nil;
_monster = [[Monster alloc] initWithContext:_context];
_jsonString = @"{\"name\":\"Acolyte\",\"size\":\"medium\",\"type\":\"humanoid\",\"tag\":\"any race\",\"alignment\":\"any alignment\",\"strPoints\":8,\"dexPoints\":10,\"conPoints\":12,\"intPoints\":14,\"wisPoints\":16,\"chaPoints\":18,\"armorName\":\"none\",\"otherArmorDesc\":\"10\",\"shieldBonus\":2,\"hitDice\":3,\"customHP\":true,\"hpText\":\"1234 (1d1+magic)\"}";
_jsonData = [_jsonString dataUsingEncoding:NSUTF8StringEncoding];
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
- (void)testDefaultInitializer {
XCTAssertNotNil(_monster);
XCTAssertEqualObjects(@"", _monster.name);
XCTAssertEqualObjects(@"", _monster.size);
XCTAssertEqualObjects(@"", _monster.type);
XCTAssertEqualObjects(@"", _monster.subtype);
XCTAssertEqualObjects(@"", _monster.alignment);
XCTAssertEqual(10, _monster.strengthScore);
XCTAssertEqual(10, _monster.dexterityScore);
XCTAssertEqual(10, _monster.constitutionScore);
XCTAssertEqual(10, _monster.intelligenceScore);
XCTAssertEqual(10, _monster.wisdomScore);
XCTAssertEqual(10, _monster.charismaScore);
XCTAssertEqualObjects(@"", _monster.armorType);
XCTAssertEqualObjects(@"", _monster.otherArmorDescription);
XCTAssertEqual(0, _monster.shieldBonus);
XCTAssertEqual(NO, _monster.customHP);
XCTAssertEqual(0, _monster.hitDice);
XCTAssertEqualObjects(@"", _monster.hpText);}
- (void)testInitWithJSONString {
_monster = [[Monster alloc] initWithJSONString:_jsonString andContext:_context];
XCTAssertNotNil(_monster);
XCTAssertEqualObjects(@"Acolyte", _monster.name);
XCTAssertEqualObjects(@"medium", _monster.size);
XCTAssertEqualObjects(@"humanoid", _monster.type);
XCTAssertEqualObjects(@"any race", _monster.subtype);
XCTAssertEqualObjects(@"any alignment", _monster.alignment);
XCTAssertEqual(8, _monster.strengthScore);
XCTAssertEqual(10, _monster.dexterityScore);
XCTAssertEqual(12, _monster.constitutionScore);
XCTAssertEqual(14, _monster.intelligenceScore);
XCTAssertEqual(16, _monster.wisdomScore);
XCTAssertEqual(18, _monster.charismaScore);
XCTAssertEqualObjects(@"none", _monster.armorType);
XCTAssertEqualObjects(@"10", _monster.otherArmorDescription);
XCTAssertEqual(2, _monster.shieldBonus);
XCTAssertEqual(YES, _monster.customHP);
XCTAssertEqual(3, _monster.hitDice);
XCTAssertEqualObjects(@"1234 (1d1+magic)", _monster.hpText);
}
- (void)testInitWithEmptyJSONString {
_monster = [[Monster alloc] initWithJSONString:@"{}" andContext:_context];
XCTAssertNotNil(_monster);
XCTAssertEqualObjects(@"", _monster.name);
XCTAssertEqualObjects(@"", _monster.size);
XCTAssertEqualObjects(@"", _monster.type);
XCTAssertEqualObjects(@"", _monster.subtype);
XCTAssertEqualObjects(@"", _monster.alignment);
XCTAssertEqual(10, _monster.strengthScore);
XCTAssertEqual(10, _monster.dexterityScore);
XCTAssertEqual(10, _monster.constitutionScore);
XCTAssertEqual(10, _monster.intelligenceScore);
XCTAssertEqual(10, _monster.wisdomScore);
XCTAssertEqual(10, _monster.charismaScore);
XCTAssertEqualObjects(@"", _monster.armorType);
XCTAssertEqualObjects(@"", _monster.otherArmorDescription);
XCTAssertEqual(0, _monster.shieldBonus);
XCTAssertEqual(NO, _monster.customHP);
XCTAssertEqual(0, _monster.hitDice);
XCTAssertEqualObjects(@"", _monster.hpText);
}
- (void)testInitWithJSONData {
_monster = [[Monster alloc] initWithJSONData:_jsonData andContext:_context];
XCTAssertNotNil(_monster);
XCTAssertEqualObjects(@"Acolyte", _monster.name);
XCTAssertEqualObjects(@"medium", _monster.size);
XCTAssertEqualObjects(@"humanoid", _monster.type);
XCTAssertEqualObjects(@"any race", _monster.subtype);
XCTAssertEqualObjects(@"any alignment", _monster.alignment);
XCTAssertEqual(8, _monster.strengthScore);
XCTAssertEqual(10, _monster.dexterityScore);
XCTAssertEqual(12, _monster.constitutionScore);
XCTAssertEqual(14, _monster.intelligenceScore);
XCTAssertEqual(16, _monster.wisdomScore);
XCTAssertEqual(18, _monster.charismaScore);
XCTAssertEqualObjects(@"none", _monster.armorType);
XCTAssertEqualObjects(@"10", _monster.otherArmorDescription);
XCTAssertEqual(2, _monster.shieldBonus);
XCTAssertEqual(YES, _monster.customHP);
XCTAssertEqual(3, _monster.hitDice);
XCTAssertEqualObjects(@"1234 (1d1+magic)", _monster.hpText);
}
- (void)testNameGetterAndSetter {
NSString *name = @"Pixie";
_monster.name = name;
XCTAssertEqualObjects(name, _monster.name);
}
- (void)testSizeGetterAndSetter {
NSString *size = @"huge";
_monster.size = size;
XCTAssertEqualObjects(size, _monster.size);
}
- (void)testTypeGetterAndSetter {
NSString *type = @"fey";
_monster.type = type;
XCTAssertEqualObjects(type, _monster.type);
}
- (void)testSubtypeGetterAndSetter {
NSString *subtype = @"elf";
_monster.subtype = subtype;
XCTAssertEqualObjects(subtype, _monster.subtype);
}
- (void)testAlignmentGetterAndSetter {
NSString *alignment = @"chaotic good";
_monster.alignment = alignment;
XCTAssertEqualObjects(alignment, _monster.alignment);
}
- (void)testCopyFromMonster {
Monster *otherMonster = [[Monster alloc] initWithJSONString:_jsonString andContext:_context];
[_monster copyFromMonster:otherMonster];
XCTAssertNotNil(_monster);
XCTAssertEqualObjects(@"Acolyte", _monster.name);
XCTAssertEqualObjects(@"medium", _monster.size);
XCTAssertEqualObjects(@"humanoid", _monster.type);
XCTAssertEqualObjects(@"any race", _monster.subtype);
XCTAssertEqualObjects(@"any alignment", _monster.alignment);
XCTAssertEqual(8, _monster.strengthScore);
XCTAssertEqual(10, _monster.dexterityScore);
XCTAssertEqual(12, _monster.constitutionScore);
XCTAssertEqual(14, _monster.intelligenceScore);
XCTAssertEqual(16, _monster.wisdomScore);
XCTAssertEqual(18, _monster.charismaScore);
XCTAssertEqualObjects(@"none", _monster.armorType);
XCTAssertEqualObjects(@"10", _monster.otherArmorDescription);
XCTAssertEqual(2, _monster.shieldBonus);
}
- (void)testMetaWithNoFieldsSet {
XCTAssertEqualObjects(@"", _monster.meta);
}
- (void)testMetaWithSize {
_monster.size = @"large";
XCTAssertEqualObjects(@"large", _monster.meta);
}
- (void)testMetaWithType {
_monster.type = @"humanoid";
XCTAssertEqualObjects(@"humanoid", _monster.meta);
}
- (void)testMetaWithSizeAndType {
_monster.size = @"large";
_monster.type = @"humanoid";
XCTAssertEqualObjects(@"large humanoid", _monster.meta);
}
- (void)testMetaWithSubtype {
_monster.subtype = @"elf";
XCTAssertEqualObjects(@"(elf)", _monster.meta);
}
- (void)testMetaWithSizeAndSubtype {
_monster.size = @"large";
_monster.subtype = @"elf";
XCTAssertEqualObjects(@"large (elf)", _monster.meta);
}
- (void)testMetaWithTypeAndSubtype {
_monster.type = @"humanoid";
_monster.subtype = @"elf";
XCTAssertEqualObjects(@"humanoid (elf)", _monster.meta);
}
- (void)testMetaWithSizeTypeAndSubtype {
_monster.size = @"large";
_monster.type = @"humanoid";
_monster.subtype = @"elf";
XCTAssertEqualObjects(@"large humanoid (elf)", _monster.meta);
}
- (void)testMetaWithAlignment {
_monster.alignment = @"chaotic good";
XCTAssertEqualObjects(@"chaotic good", _monster.meta);
}
- (void)testMetaWithSizeAndAlignment {
_monster.size = @"large";
_monster.alignment = @"chaotic good";
XCTAssertEqualObjects(@"large chaotic good", _monster.meta);
}
- (void)testMetaWithTypeAndAlignment {
_monster.type = @"humanoid";
_monster.alignment = @"chaotic good";
XCTAssertEqualObjects(@"humanoid chaotic good", _monster.meta);
}
- (void)testMetaWithSizeTypeAndAlignment {
_monster.size = @"large";
_monster.type = @"humanoid";
_monster.alignment = @"chaotic good";
XCTAssertEqualObjects(@"large humanoid chaotic good", _monster.meta);
}
- (void)testMetaWithSizeSubtypeAndAlignment {
_monster.size = @"large";
_monster.subtype = @"elf";
_monster.alignment = @"chaotic good";
XCTAssertEqualObjects(@"large (elf) chaotic good", _monster.meta);
}
- (void)testMetaWithTypeSubtypeAndAlignment {
_monster.type = @"humanoid";
_monster.subtype = @"elf";
_monster.alignment = @"chaotic good";
XCTAssertEqualObjects(@"humanoid (elf) chaotic good", _monster.meta);
}
- (void)testMetaWithSizeTypeSubtypeAndAlignment {
_monster.size = @"large";
_monster.type = @"humanoid";
_monster.subtype = @"elf";
_monster.alignment = @"chaotic good";
XCTAssertEqualObjects(@"large humanoid (elf) chaotic good", _monster.meta);
}
- (void)testAbilityModifierForScore {
XCTAssertEqual(-6, [Monster abilityModifierForScore:-1]);
XCTAssertEqual(-5, [Monster abilityModifierForScore:0]);
XCTAssertEqual(-5, [Monster abilityModifierForScore:1]);
XCTAssertEqual(-4, [Monster abilityModifierForScore:2]);
XCTAssertEqual(-4, [Monster abilityModifierForScore:3]);
XCTAssertEqual(-3, [Monster abilityModifierForScore:4]);
XCTAssertEqual(-3, [Monster abilityModifierForScore:5]);
XCTAssertEqual(-2, [Monster abilityModifierForScore:6]);
XCTAssertEqual(-2, [Monster abilityModifierForScore:7]);
XCTAssertEqual(-1, [Monster abilityModifierForScore:8]);
XCTAssertEqual(-1, [Monster abilityModifierForScore:9]);
XCTAssertEqual(0, [Monster abilityModifierForScore:10]);
XCTAssertEqual(0, [Monster abilityModifierForScore:11]);
XCTAssertEqual(1, [Monster abilityModifierForScore:12]);
XCTAssertEqual(1, [Monster abilityModifierForScore:13]);
XCTAssertEqual(2, [Monster abilityModifierForScore:14]);
XCTAssertEqual(2, [Monster abilityModifierForScore:15]);
XCTAssertEqual(3, [Monster abilityModifierForScore:16]);
XCTAssertEqual(3, [Monster abilityModifierForScore:17]);
XCTAssertEqual(4, [Monster abilityModifierForScore:18]);
XCTAssertEqual(4, [Monster abilityModifierForScore:19]);
XCTAssertEqual(5, [Monster abilityModifierForScore:20]);
}
- (void)testStrengthScoreGetterAndSetter {
_monster.strengthScore = 11;
XCTAssertEqual(11, _monster.strengthScore);
}
- (void)testStrengthModifier {
_monster.strengthScore = 9;
XCTAssertEqual(-1, _monster.strengthModifier);
_monster.strengthScore = 10;
XCTAssertEqual(0, _monster.strengthModifier);
_monster.strengthScore = 12;
XCTAssertEqual(1, _monster.strengthModifier);
}
- (void)testDexterityScoreGetterAndSetter {
_monster.dexterityScore = 11;
XCTAssertEqual(11, _monster.dexterityScore);
}
- (void)testDexterityModifier {
_monster.dexterityScore = 9;
XCTAssertEqual(-1, _monster.dexterityModifier);
_monster.dexterityScore = 10;
XCTAssertEqual(0, _monster.dexterityModifier);
_monster.dexterityScore = 12;
XCTAssertEqual(1, _monster.dexterityModifier);
}
- (void)testConstitutionScoreGetterAndSetter {
_monster.constitutionScore = 11;
XCTAssertEqual(11, _monster.constitutionScore);
}
- (void)testConstitutionModifier {
_monster.constitutionScore = 9;
XCTAssertEqual(-1, _monster.constitutionModifier);
_monster.constitutionScore = 10;
XCTAssertEqual(0, _monster.constitutionModifier);
_monster.constitutionScore = 12;
XCTAssertEqual(1, _monster.constitutionModifier);
}
- (void)testIntelligenceScoreGetterAndSetter {
_monster.intelligenceScore = 11;
XCTAssertEqual(11, _monster.intelligenceScore);
}
- (void)testIntelligenceModifier {
_monster.intelligenceScore = 9;
XCTAssertEqual(-1, _monster.intelligenceModifier);
_monster.intelligenceScore = 10;
XCTAssertEqual(0, _monster.intelligenceModifier);
_monster.intelligenceScore = 12;
XCTAssertEqual(1, _monster.intelligenceModifier);
}
- (void)testWisdomScoreGetterAndSetter {
_monster.wisdomScore = 11;
XCTAssertEqual(11, _monster.wisdomScore);
}
- (void)testWisdomModifier {
_monster.wisdomScore = 9;
XCTAssertEqual(-1, _monster.wisdomModifier);
_monster.wisdomScore = 10;
XCTAssertEqual(0, _monster.wisdomModifier);
_monster.wisdomScore = 12;
XCTAssertEqual(1, _monster.wisdomModifier);
}
- (void)testCharismaScoreGetterAndSetter {
_monster.charismaScore = 11;
XCTAssertEqual(11, _monster.charismaScore);
}
- (void)testCharismaModifier {
_monster.charismaScore = 9;
XCTAssertEqual(-1, _monster.charismaModifier);
_monster.charismaScore = 10;
XCTAssertEqual(0, _monster.charismaModifier);
_monster.charismaScore = 12;
XCTAssertEqual(1, _monster.charismaModifier);
}
- (void)testGetterAndSetterForArmorName {
_monster.armorType = @"dandelion";
XCTAssertEqualObjects(@"dandelion", _monster.armorType);
}
- (void)testGetterAndSetterForOtherArmorDescription {
_monster.otherArmorDescription = @"green";
XCTAssertEqualObjects(@"green", _monster.otherArmorDescription);
}
- (void)testHitDieForSizeReturnsExpectedValuesForKnownSizes {
}
- (void)testHitDieForSizeReutnrsEightForUnknownSizes {
XCTAssertEqual(4, [Monster hitDieForSize:kMonsterSizeTiny]);
XCTAssertEqual(6, [Monster hitDieForSize:kMonsterSizeSmall]);
XCTAssertEqual(8, [Monster hitDieForSize:kMonsterSizeMedium]);
XCTAssertEqual(10, [Monster hitDieForSize:kMonsterSizeLarge]);
XCTAssertEqual(12, [Monster hitDieForSize:kMonsterSizeHuge]);
XCTAssertEqual(20, [Monster hitDieForSize:kMonsterSizeGargantuan]);
}
- (void)testCustomHPGetterAndSetter {
XCTAssertEqual(8, [Monster hitDieForSize:nil]);
XCTAssertEqual(8, [Monster hitDieForSize:@""]);
XCTAssertEqual(8, [Monster hitDieForSize:@"unknown size"]);
}
- (void)testHitDiceGetterAndSetter {
_monster.hitDice = 9;
XCTAssertEqual(9, _monster.hitDice);
}
- (void)testHPTextGetterAndSetter {
_monster.hpText = @"This is my HP.";
XCTAssertEqualObjects(@"This is my HP.", _monster.hpText);
}
- (void)testHitPointsDescriptionWithCustomHP {
_monster = [[Monster alloc] initWithJSONString:_jsonString andContext:_context];
_monster.customHP = YES;
XCTAssertEqualObjects(@"1234 (1d1+magic)", _monster.hitPointsDescription);
}
- (void)testHitPointsDescriptionWithCalculatedHP {
_monster = [[Monster alloc] initWithJSONString:_jsonString andContext:_context];
_monster.customHP = NO;
XCTAssertEqualObjects(@"20 (3d8+3)", _monster.hitPointsDescription);
}
@end

View File

@@ -1,55 +0,0 @@
//
// SavingThrowTests.m
// MonsterCardsTests
//
// Created by Tom Hicks on 9/5/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <XCTest/XCTest.h>
#import "SavingThrow.h"
@interface SavingThrowTests : XCTestCase
@end
@implementation SavingThrowTests {
SavingThrow *_savingThrow;
NSString *_name;
int _order;
}
- (void)setUp {
_savingThrow = [[SavingThrow alloc] init];
_name = @"str";
_order = 9;
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
- (void)testDefaultInitializer {
XCTAssertNotNil(_savingThrow);
XCTAssertEqualObjects(@"", _savingThrow.name);
XCTAssertEqual(-1, _savingThrow.order);
}
- (void)testInitWithNameAndOrder {
_savingThrow = [[SavingThrow alloc] initWithName:_name andOrder:_order];
XCTAssertNotNil(_savingThrow);
XCTAssertEqualObjects(_name, _savingThrow.name);
XCTAssertEqual(_order, _savingThrow.order);
}
- (void)testNameGetterAndSetter {
_savingThrow.name = _name;
XCTAssertEqualObjects(_name, _savingThrow.name);
}
- (void)testOrderGetterAndSetter {
_savingThrow.order = _order;
XCTAssertEqual(_order, _savingThrow.order);
}
@end

View File

@@ -1,106 +0,0 @@
//
// SkillTests.m
// MonsterCardsTests
//
// Created by Tom Hicks on 9/5/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <XCTest/XCTest.h>
@import OCMockito;
@import OCHamcrest;
#import "Skill.h"
@interface SkillTests : XCTestCase
@end
@implementation SkillTests {
Skill *_skill;
NSString *_name;
NSString *_abilityScoreName;
NSString *_notes;
}
- (void)setUp {
_skill = [[Skill alloc] init];
_name = @"pranking";
_abilityScoreName = @"str";
_notes = @"some notes";
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
- (void)testDefaultInitializer {
XCTAssertNotNil(_skill);
XCTAssertEqualObjects(@"", _skill.name);
XCTAssertEqualObjects(@"", _skill.abilityScoreName);
XCTAssertEqualObjects(@"", _skill.notes);
}
- (void)testInitWithNameAbilityScoreNameAndNotes {
_skill = [[Skill alloc] initWithName:_name abilityScoreName:_abilityScoreName andNotes:_notes];
XCTAssertNotNil(_skill);
XCTAssertEqualObjects(_name, _skill.name);
XCTAssertEqualObjects(_abilityScoreName, _skill.abilityScoreName);
XCTAssertEqualObjects(_notes, _skill.notes);
}
- (void)testNameGetterAndSetter {
_skill.name = _name;
XCTAssertEqualObjects(_name, _skill.name);
}
- (void)testAbilityScoreNameGetterAndSetter {
_skill.abilityScoreName = _abilityScoreName;
XCTAssertEqualObjects(_abilityScoreName, _skill.abilityScoreName);
}
- (void)testNotesGetterAndSetter {
_skill.notes = _notes;
XCTAssertEqualObjects(_notes, _skill.notes);
}
- (void)testSkillBonusForMonster {
Monster *monster = mock([Monster class]);
[given([monster abilityModifierForAbilityScoreName:_abilityScoreName]) willReturnInt:1];
stubProperty(monster, proficiencyBonus, @2);
_skill = [[Skill alloc] initWithName:_name abilityScoreName:_abilityScoreName andNotes:_notes];
XCTAssertEqual(3, [_skill skillBonusForMonster:monster]);
}
- (void)testSkillBonusForMonsterWithExpertise {
Monster *monster = mock([Monster class]);
[given([monster abilityModifierForAbilityScoreName:_abilityScoreName]) willReturnInt:1];
stubProperty(monster, proficiencyBonus, @2);
_skill = [[Skill alloc] initWithName:_name abilityScoreName:_abilityScoreName andNotes:@" (ex)"];
XCTAssertEqual(5, [_skill skillBonusForMonster:monster]);
}
- (void)testTextForMonster {
Monster *monster = mock([Monster class]);
[given([monster abilityModifierForAbilityScoreName:_abilityScoreName]) willReturnInt:1];
stubProperty(monster, proficiencyBonus, @2);
_skill = [[Skill alloc] initWithName:_name abilityScoreName:_abilityScoreName andNotes:_notes];
XCTAssertEqualObjects(@"Pranking 3", [_skill textForMonster:monster]);
}
- (void)testTextForMonsterWithExpertise {
Monster *monster = mock([Monster class]);
[given([monster abilityModifierForAbilityScoreName:_abilityScoreName]) willReturnInt:1];
stubProperty(monster, proficiencyBonus, @2);
_skill = [[Skill alloc] initWithName:_name abilityScoreName:_abilityScoreName andNotes:@" (ex)"];
XCTAssertEqualObjects(@"Pranking 5", [_skill textForMonster:monster]);
}
@end

View File

@@ -1,37 +0,0 @@
//
// MonsterCardsTests.m
// MonsterCardsTests
//
// Created by Tom Hicks on 9/2/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <XCTest/XCTest.h>
@interface MonsterCardsTests : XCTestCase
@end
@implementation MonsterCardsTests
- (void)setUp {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
- (void)testExample {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
- (void)testPerformanceExample {
// This is an example of a performance test case.
[self measureBlock:^{
// Put the code you want to measure the time of here.
}];
}
@end

View File

@@ -0,0 +1,33 @@
//
// MonsterCardsTests.swift
// MonsterCardsTests
//
// Created by Tom Hicks on 1/15/21.
//
import XCTest
@testable import MonsterCards
class MonsterCardsTests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() throws {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}

View File

@@ -1,130 +0,0 @@
//
// EditMonsterViewControllerTests.m
// MonsterCardsTests
//
// Created by Tom Hicks on 9/12/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <XCTest/XCTest.h>
@import OCMockito;
@import OCHamcrest;
#import "MCShortStringFieldTableViewCell.h"
#import "EditMonsterViewController.h"
#import "AppDelegate.h"
@interface EditMonsterViewControllerTests : XCTestCase
@end
@implementation EditMonsterViewControllerTests {
EditMonsterViewController *_viewController;
NSManagedObjectContext *_context;
AppDelegate *_appDelegate;
NSPersistentCloudKitContainer *_persistentContainer;
Monster *_monster;
}
- (void)setUp {
_monster = mock([Monster class]);
_viewController = [[EditMonsterViewController alloc] init];
_context = mock([NSManagedObjectContext class]);
_appDelegate = mock([AppDelegate class]);
_persistentContainer = mock([NSPersistentCloudKitContainer class]);
UIApplication.sharedApplication.delegate = _appDelegate;
}
- (void)tearDown {
}
- (void)testRendersSubtypeCell {
UITableView *monstersTableView = mock([UITableView class]);
NSIndexPath *path = [NSIndexPath indexPathForRow:3 inSection:0];
MCShortStringFieldTableViewCell *shortStringCell = [[MCShortStringFieldTableViewCell alloc] init];
UITextField *textField = [[UITextField alloc] init];
shortStringCell.textField = textField;
[given([monstersTableView dequeueReusableCellWithIdentifier:@"MCShortStringField"]) willReturn:shortStringCell];
_monster.subtype = @"elf";
_viewController.originalMonster = _monster;
_viewController.monsterTableView = monstersTableView;
[_viewController viewDidLoad];
[_viewController viewWillAppear:NO];
UITableViewCell *cell = [_viewController tableView:monstersTableView cellForRowAtIndexPath:path];
XCTAssertNotNil(cell);
XCTAssertTrue([cell isKindOfClass:[MCShortStringFieldTableViewCell class]]);
shortStringCell = (MCShortStringFieldTableViewCell*)cell;
XCTAssertEqualObjects(@"monster.subtype", shortStringCell.identifier);
XCTAssertEqualObjects(@"Subtype", shortStringCell.textField.placeholder);
XCTAssertEqualObjects(@"", shortStringCell.textField.text);
XCTAssertEqual(_viewController, shortStringCell.delegate);
}
- (void)testEditingSubtype {
UIViewController *destinationVC = mock([UIViewController class]);
UIStoryboardSegue *segue = [UIStoryboardSegue segueWithIdentifier:@"SaveChanges" source:_viewController destination:destinationVC performHandler:^{}];
_monster = [[Monster alloc] initWithContext:_context];
_monster.subtype = @"elf";
_viewController.originalMonster = _monster;
[_viewController viewDidLoad];
[_viewController viewWillAppear:NO];
[_viewController editableValueDidChange:@"newValue" forIdentifier:@"monster.subtype" andType:@"String"];
[_viewController prepareForSegue:segue sender:nil];
XCTAssertEqualObjects(@"newValue", _viewController.originalMonster.subtype);
}
- (void)testRendersAlignmentCell {
UITableView *monstersTableView = mock([UITableView class]);
NSIndexPath *path = [NSIndexPath indexPathForRow:4 inSection:0];
MCShortStringFieldTableViewCell *shortStringCell = [[MCShortStringFieldTableViewCell alloc] init];
UITextField *textField = [[UITextField alloc] init];
shortStringCell.textField = textField;
[given([monstersTableView dequeueReusableCellWithIdentifier:@"MCShortStringField"]) willReturn:shortStringCell];
_monster.alignment = @"chaotic good";
_viewController.originalMonster = _monster;
_viewController.monsterTableView = monstersTableView;
[_viewController viewDidLoad];
[_viewController viewWillAppear:NO];
UITableViewCell *cell = [_viewController tableView:monstersTableView cellForRowAtIndexPath:path];
XCTAssertNotNil(cell);
XCTAssertTrue([cell isKindOfClass:[MCShortStringFieldTableViewCell class]]);
shortStringCell = (MCShortStringFieldTableViewCell*)cell;
XCTAssertEqualObjects(@"monster.alignment", shortStringCell.identifier);
XCTAssertEqualObjects(@"Alignment", shortStringCell.textField.placeholder);
XCTAssertEqualObjects(@"", shortStringCell.textField.text);
XCTAssertEqual(_viewController, shortStringCell.delegate);
}
- (void)testEditingAlignment {
UIViewController *destinationVC = mock([UIViewController class]);
UIStoryboardSegue *segue = [UIStoryboardSegue segueWithIdentifier:@"SaveChanges" source:_viewController destination:destinationVC performHandler:^{}];
_monster = [[Monster alloc] initWithContext:_context];
_monster.alignment = @"chaotic good";
_viewController.originalMonster = _monster;
[_viewController viewDidLoad];
[_viewController viewWillAppear:NO];
[_viewController editableValueDidChange:@"newValue" forIdentifier:@"monster.alignment" andType:@"String"];
[_viewController prepareForSegue:segue sender:nil];
XCTAssertEqualObjects(@"newValue", _viewController.originalMonster.alignment);
}
@end

View File

@@ -1,48 +0,0 @@
//
// MonsterCardsUITests.m
// MonsterCardsUITests
//
// Created by Tom Hicks on 9/2/20.
// Copyright © 2020 Tom Hicks. All rights reserved.
//
#import <XCTest/XCTest.h>
@interface MonsterCardsUITests : XCTestCase
@end
@implementation MonsterCardsUITests
- (void)setUp {
// Put setup code here. This method is called before the invocation of each test method in the class.
// In UI tests it is usually best to stop immediately when a failure occurs.
self.continueAfterFailure = NO;
// In UI tests its important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
- (void)testExample {
// UI tests must launch the application that they test.
XCUIApplication *app = [[XCUIApplication alloc] init];
[app launch];
// Use recording to get started writing UI tests.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
- (void)testLaunchPerformance {
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
// This measures how long it takes to launch your application.
[self measureWithMetrics:@[XCTOSSignpostMetric.applicationLaunchMetric] block:^{
[[[XCUIApplication alloc] init] launch];
}];
}
}
@end

View File

@@ -0,0 +1,42 @@
//
// MonsterCardsUITests.swift
// MonsterCardsUITests
//
// Created by Tom Hicks on 1/15/21.
//
import XCTest
class MonsterCardsUITests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
// In UI tests it is usually best to stop immediately when a failure occurs.
continueAfterFailure = false
// In UI tests its important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() throws {
// UI tests must launch the application that they test.
let app = XCUIApplication()
app.launch()
// Use recording to get started writing UI tests.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testLaunchPerformance() throws {
if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {
// This measures how long it takes to launch your application.
measure(metrics: [XCTApplicationLaunchMetric()]) {
XCUIApplication().launch()
}
}
}
}

View File

@@ -1,23 +0,0 @@
# Uncomment the next line to define a global platform for your project
platform :ios, '12.0'
target 'MonsterCards' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# Pods for MonsterCards
target 'MonsterCardsTests' do
inherit! :search_paths
# Pods for testing
use_frameworks!
pod 'OCMockito', '~> 5.0'
end
target 'MonsterCardsUITests' do
# Pods for testing
use_frameworks!
pod 'OCMockito', '~> 5.0'
end
end

View File

@@ -1,20 +0,0 @@
PODS:
- OCHamcrest (7.1.2)
- OCMockito (5.1.3):
- OCHamcrest (~> 7.0)
DEPENDENCIES:
- OCMockito (~> 5.0)
SPEC REPOS:
trunk:
- OCHamcrest
- OCMockito
SPEC CHECKSUMS:
OCHamcrest: b284c9592c28c1e4025a8542e67ea41a635d0d73
OCMockito: 677cbb4a18fd492b5a4fb10144dada4de5ddb877
PODFILE CHECKSUM: 36e78345fa3ece507b6ca1b4a3033ab665fec396
COCOAPODS: 1.9.3

Some files were not shown because too many files have changed in this diff Show More