Reorganized the MonsterDetail view to get around the 10 items per group limit.

Adds layout for resistances, immunities, and languages.
This commit is contained in:
2021-03-22 00:58:26 -07:00
parent a59571aae8
commit 67c3ec480d
7 changed files with 408 additions and 156 deletions

View File

@@ -42,6 +42,10 @@
E2BD703125B3BBB90058ED69 /* MCStepperField.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2BD703025B3BBB90058ED69 /* MCStepperField.swift */; }; E2BD703125B3BBB90058ED69 /* MCStepperField.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2BD703025B3BBB90058ED69 /* MCStepperField.swift */; };
E2CB0DB326080C0500142591 /* EditSkill.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2CB0DB226080C0500142591 /* EditSkill.swift */; }; E2CB0DB326080C0500142591 /* EditSkill.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2CB0DB226080C0500142591 /* EditSkill.swift */; };
E2CB0DB826081A2F00142591 /* MCAbilityScorePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2CB0DB726081A2F00142591 /* MCAbilityScorePicker.swift */; }; E2CB0DB826081A2F00142591 /* MCAbilityScorePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2CB0DB726081A2F00142591 /* MCAbilityScorePicker.swift */; };
E2CB0DC026086E3C00142591 /* ChallengeRating.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2CB0DBF26086E3C00142591 /* ChallengeRating.swift */; };
E2CB0DC526086E5F00142591 /* SizeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2CB0DC426086E5F00142591 /* SizeType.swift */; };
E2CB0DCA26086E8300142591 /* ArmorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2CB0DC926086E8300142591 /* ArmorType.swift */; };
E2CB0DD72608720000142591 /* StringHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2CB0DD62608720000142591 /* StringHelper.swift */; };
E2D473FD25B532C900CB36D7 /* Color+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2D473FC25B532C900CB36D7 /* Color+Hex.swift */; }; E2D473FD25B532C900CB36D7 /* Color+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2D473FC25B532C900CB36D7 /* Color+Hex.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@@ -104,6 +108,10 @@
E2BD703025B3BBB90058ED69 /* MCStepperField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCStepperField.swift; sourceTree = "<group>"; }; E2BD703025B3BBB90058ED69 /* MCStepperField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCStepperField.swift; sourceTree = "<group>"; };
E2CB0DB226080C0500142591 /* EditSkill.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditSkill.swift; sourceTree = "<group>"; }; E2CB0DB226080C0500142591 /* EditSkill.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditSkill.swift; sourceTree = "<group>"; };
E2CB0DB726081A2F00142591 /* MCAbilityScorePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCAbilityScorePicker.swift; sourceTree = "<group>"; }; E2CB0DB726081A2F00142591 /* MCAbilityScorePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCAbilityScorePicker.swift; sourceTree = "<group>"; };
E2CB0DBF26086E3C00142591 /* ChallengeRating.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChallengeRating.swift; sourceTree = "<group>"; };
E2CB0DC426086E5F00142591 /* SizeType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizeType.swift; sourceTree = "<group>"; };
E2CB0DC926086E8300142591 /* ArmorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArmorType.swift; sourceTree = "<group>"; };
E2CB0DD62608720000142591 /* StringHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringHelper.swift; sourceTree = "<group>"; };
E2D473FC25B532C900CB36D7 /* Color+Hex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Hex.swift"; sourceTree = "<group>"; }; E2D473FC25B532C900CB36D7 /* Color+Hex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Hex.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@@ -137,7 +145,10 @@
children = ( children = (
E20209E725D8DEC100EFE733 /* AbilityScore.swift */, E20209E725D8DEC100EFE733 /* AbilityScore.swift */,
E20209F325D8E04300EFE733 /* AdvantageType.swift */, E20209F325D8E04300EFE733 /* AdvantageType.swift */,
E2CB0DC926086E8300142591 /* ArmorType.swift */,
E2CB0DBF26086E3C00142591 /* ChallengeRating.swift */,
E20209F225D8E04300EFE733 /* ProficiencyType.swift */, E20209F225D8E04300EFE733 /* ProficiencyType.swift */,
E2CB0DC426086E5F00142591 /* SizeType.swift */,
); );
path = Enums; path = Enums;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -248,6 +259,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E2D473FC25B532C900CB36D7 /* Color+Hex.swift */, E2D473FC25B532C900CB36D7 /* Color+Hex.swift */,
E2CB0DD62608720000142591 /* StringHelper.swift */,
); );
path = Helpers; path = Helpers;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -393,8 +405,10 @@
E257100925B1B2480055B23B /* MonsterDetail.swift in Sources */, E257100925B1B2480055B23B /* MonsterDetail.swift in Sources */,
E2D473FD25B532C900CB36D7 /* Color+Hex.swift in Sources */, E2D473FD25B532C900CB36D7 /* Color+Hex.swift in Sources */,
E2B5285925B3028700AAA69E /* EditMonster.swift in Sources */, E2B5285925B3028700AAA69E /* EditMonster.swift in Sources */,
E2CB0DD72608720000142591 /* StringHelper.swift in Sources */,
E2570FF525B1ADEB0055B23B /* Dashboard.swift in Sources */, E2570FF525B1ADEB0055B23B /* Dashboard.swift in Sources */,
E2CB0DB826081A2F00142591 /* MCAbilityScorePicker.swift in Sources */, E2CB0DB826081A2F00142591 /* MCAbilityScorePicker.swift in Sources */,
E2CB0DC026086E3C00142591 /* ChallengeRating.swift in Sources */,
E257100425B1AF4A0055B23B /* SearchBar.swift in Sources */, E257100425B1AF4A0055B23B /* SearchBar.swift in Sources */,
E20209F525D8E04300EFE733 /* AdvantageType.swift in Sources */, E20209F525D8E04300EFE733 /* AdvantageType.swift in Sources */,
E24ACE6A2607F715009BF703 /* EditSkills.swift in Sources */, E24ACE6A2607F715009BF703 /* EditSkills.swift in Sources */,
@@ -402,8 +416,10 @@
E2570FFF25B1AE180055B23B /* Library.swift in Sources */, E2570FFF25B1AE180055B23B /* Library.swift in Sources */,
E2BD703125B3BBB90058ED69 /* MCStepperField.swift in Sources */, E2BD703125B3BBB90058ED69 /* MCStepperField.swift in Sources */,
E2CB0DB326080C0500142591 /* EditSkill.swift in Sources */, E2CB0DB326080C0500142591 /* EditSkill.swift in Sources */,
E2CB0DCA26086E8300142591 /* ArmorType.swift in Sources */,
E24ACE562607EE94009BF703 /* EditArmor.swift in Sources */, E24ACE562607EE94009BF703 /* EditArmor.swift in Sources */,
E20209F425D8E04300EFE733 /* ProficiencyType.swift in Sources */, E20209F425D8E04300EFE733 /* ProficiencyType.swift in Sources */,
E2CB0DC526086E5F00142591 /* SizeType.swift in Sources */,
E2570FFA25B1AE020055B23B /* Collections.swift in Sources */, E2570FFA25B1AE020055B23B /* Collections.swift in Sources */,
E24ACE5B2607F0F2009BF703 /* EditSpeed.swift in Sources */, E24ACE5B2607F0F2009BF703 /* EditSpeed.swift in Sources */,
E2570FB925B1AC520055B23B /* MonsterCardsApp.swift in Sources */, E2570FB925B1AC520055B23B /* MonsterCardsApp.swift in Sources */,

View File

@@ -0,0 +1,40 @@
//
// StringHelper.swift
// MonsterCards
//
// Created by Tom Hicks on 3/21/21.
//
import Foundation
class StringHelper {
static func oxfordJoin(
_ strings: [String],
_ separator: String,
_ lastSeparator: String,
_ onlySeparator: String
) -> String {
let numStrings = strings.count
if (numStrings < 1) {
return "";
} else if (numStrings == 2) {
return strings[0] + onlySeparator + strings[1]
} else {
var joined = ""
var index = 0
let lastIndex = numStrings - 1
strings.forEach {
if index > 0 && index < lastIndex {
joined.append(separator)
} else if (index > 0 && index >= lastIndex) {
joined.append(lastSeparator)
}
joined.append($0)
index = index + 1
}
return joined
}
}
}

View File

@@ -0,0 +1,50 @@
//
// ArmorType.swift
// MonsterCards
//
// Created by Tom Hicks on 3/21/21.
//
import Foundation
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"
}
}
}

View File

@@ -0,0 +1,53 @@
//
// ChallengeRating.swift
// MonsterCards
//
// Created by Tom Hicks on 3/21/21.
//
import Foundation
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

@@ -0,0 +1,30 @@
//
// SizeType.swift
// MonsterCards
//
// Created by Tom Hicks on 3/21/21.
//
import Foundation
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"
}
}
}

View File

@@ -625,115 +625,84 @@ public class Monster: NSManagedObject {
} }
} }
// MARK: OTHER
var damageVulnerabilitiesArray: [String] {
get {
return ["Fire", "Poison", "Psychic"]
}
}
var damageVulnerabilitiesDescription: String {
get {
let sortedVulnerabilities = self.damageVulnerabilitiesArray.sorted()
return StringHelper.oxfordJoin(sortedVulnerabilities, ", ", ", and ", " and ")
}
}
var damageResistancesArray: [String] {
get {
return ["Ice", "Electric"]
}
}
var damageResistancesDescription: String {
get {
let sortedResistances = self.damageResistancesArray.sorted()
return StringHelper.oxfordJoin(sortedResistances, ", ", ", and ", " and ")
}
}
var damageImmunitiesArray: [String] {
get {
return ["Slashing"]
}
}
var damageImmunitiesDescription: String {
get {
let sortedImmunities = self.damageImmunitiesArray.sorted()
return StringHelper.oxfordJoin(sortedImmunities, ", ", ", and ", " and ")
}
}
var conditionImmunitiesArray: [String] {
get {
return []
}
}
var conditionImmunitiesDescription: String {
get {
let sortedImmunities = self.conditionImmunitiesArray.sorted()
return StringHelper.oxfordJoin(sortedImmunities, ", ", ", and ", " and ")
}
}
var languagesArray: [String] {
get {
return ["Common", "Goblin"]
}
}
var languagesDescription: String {
get {
let sortedLanguages = self.languagesArray.sorted()
return StringHelper.oxfordJoin(sortedLanguages, ", ", ", and ", " and ")
}
}
var challengeRatingDescription: String {
get {
return "";
}
}
// MARK: End // 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

@@ -70,6 +70,127 @@ struct SmallAbilityScore: View {
} }
} }
struct BasicInfoView: View {
@ObservedObject var monster: Monster
var body: some View {
let monsterMeta = monster.meta
let monsterArmorClassDescription = monster.armorClassDescription
let monsterHitPoints = monster.hitPoints
let monsterSpeed = monster.speed
// meta: "(large humanoid (elf) lawful evil"
if (!monsterMeta.isEmpty) {
Text(monsterMeta)
.font(.subheadline)
.foregroundColor(.secondary)
}
SectionDivider()
// AC
if (!monsterArmorClassDescription.isEmpty) {
LabeledField("Armor Class") {
Text(monsterArmorClassDescription)// armor class
}
}
// HP
if (!monsterHitPoints.isEmpty) {
LabeledField("Hit Points") {
Text(monsterHitPoints) // hit points
}
}
// Speed
if (!monsterSpeed.isEmpty) {
LabeledField("Speed") {
Text(monsterSpeed) // speed
}
}
}
}
struct AbilityScoresView: View {
@ObservedObject var monster: Monster
var body: some View {
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)
}
}
}
struct ResistancesAndImmunitiesView: View {
@ObservedObject var monster: Monster
var body: some View {
let monsterDamageVulnerabilitiesDescription = monster.damageVulnerabilitiesDescription
let monsterDamageResistancesDescription = monster.damageResistancesDescription
let monsterDamageImmunitiesDescription = monster.damageImmunitiesDescription
let monsterConditionImmunitiesDescription = monster.conditionImmunitiesDescription
// Damage Vulnerabilities
if (!monsterDamageVulnerabilitiesDescription.isEmpty) {
LabeledField("Damage Vulnerabilities") {
Text(monsterDamageVulnerabilitiesDescription)
}
}
// Damage Resistances
if (!monsterDamageResistancesDescription.isEmpty) {
LabeledField("Damage Resistances") {
Text(monsterDamageResistancesDescription)
}
}
// Damage Immunities
if (!monsterDamageImmunitiesDescription.isEmpty) {
LabeledField("Damage Immunities") {
Text(monsterDamageImmunitiesDescription)
}
}
// Condition Immunities
if (!monsterConditionImmunitiesDescription.isEmpty) {
LabeledField("Condition Immunities") {
Text(monsterConditionImmunitiesDescription)
}
}
}
}
struct SavingThrowsAndSkillsView: View {
@ObservedObject var monster: Monster
var body: some View {
let savingThrowsDescription = monster.savingThrowsDescription
let skillsDescription = monster.skillsDescription
// Saving Throws
if (!savingThrowsDescription.isEmpty) {
LabeledField("Saving Throws") {
Text(savingThrowsDescription)
}
}
// Skills
if (!skillsDescription.isEmpty) {
LabeledField("Skills") {
Text(skillsDescription)
}
}
}
}
struct MonsterDetail: View { struct MonsterDetail: View {
let kTextColor: Color = Color(hex: 0x982818) let kTextColor: Color = Color(hex: 0x982818)
@@ -78,72 +199,45 @@ struct MonsterDetail: View {
var body: some View { var body: some View {
ScrollView { ScrollView {
VStack (alignment: .leading) { VStack (alignment: .leading) {
let monsterMeta = monster.meta let monsterLanguagesDescription = monster.languagesDescription
let monsterArmorClassDescription = monster.armorClassDescription let monsterChallengeRatingDescription = monster.challengeRatingDescription
let monsterHitPoints = monster.hitPoints
let monsterSpeed = monster.speed
if (!monsterMeta.isEmpty) { BasicInfoView(monster: monster)
// meta: "(large humanoid (elf) lawful evil"
Text(monsterMeta)
.font(.subheadline)
.foregroundColor(.secondary)
}
// TODO: Find a way to hide unnecessarry dividiers. // TODO: Find a way to hide unnecessarry dividiers.
// if sections 0, 1, 2, and 3 are present there should be a divider between each of them // if sections 0, 1, 2, and 3 are present there should be a divider between each of them
// if section 1 is not present there should be one and only one divider between sections 0 and 2 as well as the one between 2 and 3 // if section 1 is not present there should be one and only one divider between sections 0 and 2 as well as the one between 2 and 3
// if sections 1 and 2 are not present there should be a single divider between sections 0 and 3 // if sections 1 and 2 are not present there should be a single divider between sections 0 and 3
SectionDivider()
if (!monsterArmorClassDescription.isEmpty) { AbilityScoresView(monster: monster)
// AC
LabeledField("Armor Class") {
Text(monsterArmorClassDescription)// armor class
}
}
if (!monsterHitPoints.isEmpty) {
// HP
LabeledField("Hit Points") {
Text(monsterHitPoints) // hit points
}
}
// Speed
if (!monsterSpeed.isEmpty) {
LabeledField("Speed") {
Text(monsterSpeed) // speed
}
}
SectionDivider() SectionDivider()
// Ability Scores SavingThrowsAndSkillsView(monster: monster)
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 ResistancesAndImmunitiesView(monster: monster)
if (!savingThrowsDescription.isEmpty) {
LabeledField("Saving Throws") { // Languages
Text(savingThrowsDescription) if (!monsterLanguagesDescription.isEmpty) {
LabeledField("Languages") {
Text(monsterLanguagesDescription)
} }
} }
let skillsDescription = monster.skillsDescription // Challenge Rating
if (!skillsDescription.isEmpty) { if (!monsterChallengeRatingDescription.isEmpty) {
LabeledField("Skills") { LabeledField("Challenge Rating") {
Text(skillsDescription) Text(monsterChallengeRatingDescription)
} }
} }
// Abilities
// Actions
// Legendary Actions
} }
.padding(.horizontal) .padding(.horizontal)
.foregroundColor(kTextColor) .foregroundColor(kTextColor)