Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
2ede0c6
Switch FFV over to List and update underlying views accordingly
dfeinzimer Aug 15, 2025
f702a8e
Add section header note
dfeinzimer Aug 18, 2025
c4b4d77
scrollViewProxy -> scrollView
dfeinzimer Aug 18, 2025
c3553fb
Update TextInput.swift
dfeinzimer Aug 18, 2025
d113e37
Increase tap areas
dfeinzimer Aug 18, 2025
5fcb01a
Fix tap area on blanks
dfeinzimer Aug 18, 2025
533bf1a
Non-editable icon
dfeinzimer Aug 18, 2025
2a7da4e
Update FormElementHeader.swift
dfeinzimer Aug 18, 2025
e118a8d
Fix group form elements
dfeinzimer Aug 19, 2025
ab738e1
Remove FeatureFormGroupedContentView
dfeinzimer Aug 19, 2025
403f778
Update FormElementFooter.swift
dfeinzimer Aug 19, 2025
74b9213
Remove FormElementWrapper
dfeinzimer Aug 19, 2025
d50ee3c
Update DateTimeInput.swift
dfeinzimer Aug 19, 2025
d2433c8
Fix disappearing sheet
dfeinzimer Aug 20, 2025
42134d7
Update TextInput.swift
dfeinzimer Aug 20, 2025
6557de1
Update UtilityAssociationsFormElementView.swift
dfeinzimer Aug 20, 2025
e505d3d
Test updates
dfeinzimer Aug 20, 2025
9d6817a
Test updates
dfeinzimer Aug 20, 2025
6013292
Update FeatureFormViewTests.swift
dfeinzimer Aug 20, 2025
a1257a4
Test updates
dfeinzimer Aug 20, 2025
ceb3c79
Update FeatureFormViewTests.swift
dfeinzimer Aug 21, 2025
e4d7556
Update FeatureFormViewTests.swift
dfeinzimer Aug 21, 2025
903f4fe
Update FeatureFormViewTests.swift
dfeinzimer Aug 21, 2025
a8f3a5c
Update FeatureFormViewTests.swift
dfeinzimer Aug 21, 2025
7018681
Update FeatureFormViewTests.swift
dfeinzimer Aug 21, 2025
604a0f3
Update FeatureFormViewTests.swift
dfeinzimer Aug 21, 2025
d34c19c
Update FeatureFormViewTests.swift
dfeinzimer Aug 21, 2025
1d57ddf
Test cleanup
dfeinzimer Aug 21, 2025
72595d9
Update FeatureFormViewTests.swift
dfeinzimer Aug 21, 2025
fc7cff2
Update FeatureFormViewTests.swift
dfeinzimer Aug 21, 2025
8b3cdc0
Update FeatureFormViewTests.swift
dfeinzimer Aug 21, 2025
0eac5d7
Update FeatureFormViewTests.swift
dfeinzimer Aug 21, 2025
4f521e8
Update FeatureFormViewTests.swift
dfeinzimer Aug 21, 2025
47272b1
Test fixes
dfeinzimer Aug 21, 2025
a8e1172
Test fixes
dfeinzimer Aug 21, 2025
2505c52
Update FeatureFormViewTests.swift
dfeinzimer Aug 21, 2025
f50ce6f
Update FeatureFormViewTests.swift
dfeinzimer Aug 21, 2025
0497ef5
Update FeatureFormViewTests.swift
dfeinzimer Aug 21, 2025
93dc70a
Update FeatureFormViewTests.swift
dfeinzimer Aug 21, 2025
a29be88
Update FeatureFormViewTests.swift
dfeinzimer Aug 21, 2025
81cf6da
More test fixes
dfeinzimer Aug 21, 2025
6f8ad10
Update FeatureFormViewTests.swift
dfeinzimer Aug 21, 2025
4a181ce
Update FeatureFormViewTests.swift
dfeinzimer Aug 21, 2025
520c75b
Update FeatureFormViewTests.swift
dfeinzimer Aug 21, 2025
f7c781d
Update FeatureFormViewTests.swift
dfeinzimer Aug 21, 2025
8e1604f
Update FeatureFormViewTests.swift
dfeinzimer Aug 22, 2025
43918b5
Skip some tests on Mac Catalyst
dfeinzimer Aug 22, 2025
ed52e96
Update FeatureFormViewTests.swift
dfeinzimer Aug 22, 2025
9edd2a5
More test fixes
dfeinzimer Aug 22, 2025
69cba54
Update FeatureFormViewTests.swift
dfeinzimer Aug 22, 2025
19b244c
Update FeatureFormViewTests.swift
dfeinzimer Aug 22, 2025
55052e1
Update FeatureFormViewTests.swift
dfeinzimer Aug 22, 2025
bd90ce0
Merge remote-tracking branch 'origin/philium/remove-deprecated-APIs' …
dfeinzimer Aug 25, 2025
a2488e7
Merge branch 'v.next' into df/FFV_As_List
dfeinzimer Sep 4, 2025
4657177
Update EmbeddedFeatureFormView.swift
dfeinzimer Sep 4, 2025
bfc365e
Merge branch 'v.next' into df/FFV_As_List
dfeinzimer Sep 8, 2025
0585a06
Merge branch 'v.next' into df/FFV_As_List
dfeinzimer Sep 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,15 @@ struct EmbeddedFeatureFormView: View {
}

var body: some View {
ScrollViewReader { scrollViewProxy in
ScrollView {
VStack(alignment: .leading) {
ForEach(embeddedFeatureFormViewModel.visibleElements, id: \.self) { element in
makeElement(element)
}
}
ScrollViewReader { scrollView in
List(embeddedFeatureFormViewModel.visibleElements, id: \.self) { element in
makeElement(element)
}
.onChange(of: embeddedFeatureFormViewModel.focusedElement) {
if let focusedElement = embeddedFeatureFormViewModel.focusedElement {
withAnimation { scrollViewProxy.scrollTo(focusedElement, anchor: .top) }
// Navigation bars will unfortunately cover or obscure
// section headers. See FB19740517.
withAnimation { scrollView.scrollTo(focusedElement, anchor: .top) }
}
}
.onTitleChange(of: embeddedFeatureFormViewModel.featureForm) { newTitle in
Expand All @@ -49,7 +47,6 @@ struct EmbeddedFeatureFormView: View {
.scrollDismissesKeyboard(.immediately)
#endif
.environment(embeddedFeatureFormViewModel)
.padding(.horizontal)
.preference(
key: PresentedFeatureFormPreferenceKey.self,
value: .init(object: embeddedFeatureFormViewModel.featureForm)
Expand All @@ -62,11 +59,19 @@ extension EmbeddedFeatureFormView {
/// Makes UI for a form element.
/// - Parameter element: The element to generate UI for.
@ViewBuilder func makeElement(_ element: FormElement) -> some View {
switch element {
case let element as GroupFormElement:
GroupFormElementView(element: element) { internalMakeElement($0) }
default:
internalMakeElement(element)
Section {
switch element {
case let element as GroupFormElement:
GroupFormElementView(element: element) { internalMakeElement($0) }
default:
internalMakeElement(element)
}
} header: {
FormElementHeader(element: element)
.textCase(.none)
} footer: {
FormElementFooter(element: element)
.textCase(.none)
}
}

Expand Down Expand Up @@ -94,22 +99,19 @@ extension EmbeddedFeatureFormView {
/// - Parameter element: The element to generate UI for.
@ViewBuilder func makeFieldElement(_ element: FieldFormElement) -> some View {
if !(element.input is UnsupportedFormInput) {
FormElementWrapper(element: element)
Divider()
FieldFormElementView(element: element)
}
}

/// Makes UI for a text form element including a divider beneath it.
/// - Parameter element: The element to generate UI for.
@ViewBuilder func makeTextElement(_ element: TextFormElement) -> some View {
TextFormElementView(element: element)
Divider()
}

/// Makes UI for a utility associations element including a divider beneath it.
/// - Parameter element: The element to generate UI for.
@ViewBuilder func makeUtilityAssociationsFormElement(_ element: UtilityAssociationsFormElement) -> some View {
FormElementWrapper(element: element)
Divider()
FeatureFormView.UtilityAssociationsFormElementView(element: element)
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import ArcGIS
import SwiftUI

/// A view which creates the correct input view based on the field form element's input type and editable state.
struct FieldFormElementView: View {
/// A Boolean value indicating whether the input is editable.
@State private var isEditable = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ struct FormElementFooter: View {
case let element as UtilityAssociationsFormElement:
UtilityAssociationsFormElementFooter(element: element)
default:
// GroupFormElement's description is shown in the DisclosureGroup's
// label.
EmptyView()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ struct FormElementHeader: View {
titleTextForElement
.font(.subheadline)
.foregroundStyle(.secondary)
Spacer()
}
.padding(.top, formElementPadding)
}
Expand All @@ -37,10 +36,8 @@ struct FormElementHeader: View {
switch element {
case let element as FieldFormElement:
FieldFormElementTitle(element: element)
case let element as UtilityAssociationsFormElement:
Text(element.label)
default:
EmptyView()
Text(element.label)
}
}
}
Expand All @@ -63,6 +60,10 @@ extension FormElementHeader {
.onIsRequiredChange(of: element) { newIsRequired in
isRequired = newIsRequired
}
Spacer()
if !isEditable {
Image(systemName: "pencil.slash")
}
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,15 @@ struct GroupFormElementView<Content>: View where Content: View {
var body: some View {
DisclosureGroup(isExpanded: $isExpanded) {
ForEach(visibleElements, id: \.self) { element in
viewCreator(element)
.padding(.leading, 16)
VStack(alignment: .leading) {
FormElementHeader(element: element)
viewCreator(element)
.formInputStyle(isTappable: true)
FormElementFooter(element: element)
}
}
} label: {
Header(element: element)
Label(element: element)
.multilineTextAlignment(.leading)
.tint(.primary)
}
Expand Down Expand Up @@ -67,21 +71,15 @@ struct GroupFormElementView<Content>: View where Content: View {

extension GroupFormElementView {
/// A view displaying a label and description of a `GroupFormElement`.
struct Header: View {
struct Label: View {
let element: GroupFormElement

var body: some View {
VStack(alignment: .leading) {
if !element.label.isEmpty {
Text(element.label)
.accessibilityIdentifier("\(element.label)")
}
if !element.description.isEmpty {
Text(element.description)
.accessibilityIdentifier("\(element.label) Description")
.font(.caption2)
.foregroundStyle(.secondary)
}
if !element.description.isEmpty {
Text(element.description)
.accessibilityIdentifier("\(element.label) Description")
.font(.caption2)
.foregroundStyle(.secondary)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,21 @@ extension FeatureFormView {
switch associationsFilterResultsModel.result {
case .success(let results):
if results.isEmpty {
FeatureFormGroupedContentView(
content: [Text.noAssociations]
)
Text.noAssociations
} else {
FeatureFormGroupedContentView(content: results.map {
ForEach(results.indices) { index in
UtilityAssociationsFilterResultListRowView(
associationsFilterResultsModel: associationsFilterResultsModel,
element: element,
filterTitle: $0.filter.title
)
.environment(embeddedFeatureFormViewModel)
})
}
}
case .failure(let error):
FeatureFormGroupedContentView(content: [
Text.errorFetchingFilterResults(error)
])
case .none:
FeatureFormGroupedContentView(content: [ProgressView()])
Text.errorFetchingFilterResults(error)
case nil:
ProgressView()
}
}
.onChange(of: embeddedFeatureFormViewModel.hasEdits) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ struct ComboBoxInput: View {
.accessibilityIdentifier("\(element.label) Combo Box Value")
.frame(maxWidth: .infinity, alignment: .leading)
.foregroundStyle(!selectedValue.isNoValue ? .primary : .secondary)
.contentShape(.rect)
.onTapGesture {
embeddedFeatureFormViewModel.focusedElement = element
isPresented = true
}
if let _ = selectedValue.codedValue, !isRequired {
// Only show clear button if we have a value
// and we're not required. (i.e., Don't show clear if
Expand All @@ -101,7 +106,6 @@ struct ComboBoxInput: View {
.foregroundStyle(.secondary)
}
}
.formInputStyle(isTappable: true)
.onIsRequiredChange(of: element) { newIsRequired in
isRequired = newIsRequired
}
Expand All @@ -116,10 +120,6 @@ struct ComboBoxInput: View {
selectedValue = .noValue
}
}
.onTapGesture {
embeddedFeatureFormViewModel.focusedElement = element
isPresented = true
}
.sheet(isPresented: $isPresented) {
makePicker()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,23 @@ struct DateTimeInput: View {
formattedDate
.accessibilityIdentifier("\(element.label) Value")
.foregroundStyle(displayColor)
.frame(maxWidth: .infinity, alignment: .leading)
.contentShape(.rect)
.onTapGesture {
withAnimation {
if date == nil {
if dateRange.contains(.now) {
date = .now
} else if let min = input.min {
date = min
} else if let max = input.max {
date = max
}
}
isEditing.toggle()
embeddedFeatureFormViewModel.focusedElement = isEditing ? element : nil
}
}

Spacer()

Expand All @@ -107,23 +124,6 @@ struct DateTimeInput: View {
}
}
}
.formInputStyle(isTappable: true)
.frame(maxWidth: .infinity)
.onTapGesture {
withAnimation {
if date == nil {
if dateRange.contains(.now) {
date = .now
} else if let min = input.min {
date = min
} else if let max = input.max {
date = max
}
}
isEditing.toggle()
embeddedFeatureFormViewModel.focusedElement = isEditing ? element : nil
}
}
}

/// The system formatted version of the element's current date.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,6 @@ struct RadioButtonsInput: View {
}
}
}
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color(uiColor: .tertiarySystemFill))
)
.frame(maxWidth: .infinity, alignment: .leading)
.onAppear {
if let selectedValue = element.codedValues.first(where: {
$0.name == element.formattedValue
Expand Down
Loading