I'm adding a side loading view to an app which is used for view and adding certain incidents. The basic layout is intended as follows:
Here is the code for this content:
var body: some View {
List {
Section(header: Text("Existing incidents")) {
ForEach(incidents) { incident in
VStack(alignment: .leading) {
HStack {
Image.General.incident
.foregroundColor(Constants.iconColor)
Text(incident.name)
.foregroundColor(Constants.textColor)
.bold()
}
Spacer()
HStack {
Image.General.clock
.foregroundColor(Constants.iconColor)
Text(incident.date)
.foregroundColor(Constants.textColor)
}
Spacer()
HStack {
Image.General.message
.foregroundColor(Constants.iconColor)
Text(incident.message)
.foregroundColor(Constants.textColor)
}
}
}
}
}
.listStyle(PlainListStyle())
NavigationView {
Section(header: Text("Add an incident")
.bold()
.foregroundColor(Constants.textColor)
.padding()) {
Form {
HStack {
Image.General.select
Spacer()
Picker("Incident selection", selection: $selectedIncident)
{
ForEach(incidentsList, id: \.self) {
Text($0)
}
}
}.padding()
HStack {
Image.General.write
Spacer()
TextField("Additional comments", text: $comments)
}.padding()
Button {
}
label: {
Text("Submit")
.foregroundColor(Color(UIColor.white))
}
.padding(.top, 5)
.padding(.bottom, 5)
.padding(.leading, 20)
.padding(.trailing, 20)
.background(Color(UIColor.i6.blue))
.cornerRadius(5)
}
}
}
.padding(.bottom)
}
}
I intent to obviously split this code out, however I am confused with the way the Form is working here inside the NavigationView. As you can see, I have a back arrow at the top of the part which leads back to a view which contains just the section header. However, I do not want to navigate backwards from the point. What I intend is to have the section header and form on this main page and the NavigationView is just to enable to picker to work, which it does as follows:
But how can remove the initial back arrow and display the section header on the first view?
CodePudding user response:
I have two potential answers. The first is fixing what you were trying to do. It works, but it is hacky and I would only use it at the end of a view hierarchy, if at all. I mocked your code into a minimal, reproducible example:
struct SplitViewFormPicker: View {
@State var incidents = [
Incident(name: "Incident 1", date: "1/1/21", message: "message 1 is here"),
Incident(name: "Incident 2", date: "1/2/21", message: "message 2 is here"),
Incident(name: "Incident 3", date: "1/3/21", message: "message 3 is here"),
Incident(name: "Incident 4", date: "1/4/21", message: "message 4 is here"),
Incident(name: "Incident 5", date: "1/5/21", message: "message 5 is here"),
Incident(name: "Incident 6", date: "1/6/21", message: "message 6 is here"),
Incident(name: "Incident 7", date: "1/7/21", message: "message 7 is here"),
Incident(name: "Incident 8", date: "1/8/21", message: "message 8 is here"),
Incident(name: "Incident 9", date: "1/9/21", message: "message 9 is here"),
Incident(name: "Incident 10", date: "1/10/21", message: "message 10 is here"),
Incident(name: "Incident 11", date: "1/11/21", message: "message 11 is here"),
]
@State var selectedIncident = ""
@State var comments = ""
let incidentsList = ["Incident 1", "Incident 2", "Incident 3", "Incident 4"]
var body: some View {
VStack {
List {
Section(header: Text("Existing incidents")) {
ForEach(incidents) { incident in
VStack(alignment: .leading) {
HStack {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundColor(Constants.iconColor)
Text(incident.name)
.foregroundColor(Constants.textColor)
.bold()
}
Spacer()
HStack {
Image(systemName: "clock")
.foregroundColor(Constants.iconColor)
Text(incident.date)
.foregroundColor(Constants.textColor)
}
Spacer()
HStack {
Image(systemName: "message.fill")
.foregroundColor(Constants.iconColor)
Text(incident.message)
.foregroundColor(Constants.textColor)
}
}
}
}
}
.listStyle(PlainListStyle())
NavigationView {
Form {
Section(header: Text("Add an incident")
.bold()
.foregroundColor(Constants.textColor)
.padding()) {
HStack {
Image(systemName: "hand.draw")
Spacer()
Picker("Incident selection", selection: $selectedIncident)
{
ForEach(incidentsList, id: \.self) {
Text($0)
}
}
}.padding()
HStack {
Image(systemName: "rectangle.and.pencil.and.ellipsis")
Spacer()
TextField("Additional comments", text: $comments)
}.padding()
Button {
}
label: {
Text("Submit")
.foregroundColor(Color(UIColor.white))
}
.padding(.top, 5)
.padding(.bottom, 5)
.padding(.leading, 20)
.padding(.trailing, 20)
.background(Color(UIColor.systemGray6))
.cornerRadius(5)
}
.navigationBarHidden(true)
}
}
.padding(.bottom)
}
}
}
struct Constants {
static var textColor = Color.black
static let iconColor = Color.orange
}
struct Incident: Identifiable {
let id = UUID()
let name: String
let date: String
let message: String
}
The second, is flexible, reusable and not hacky in the least:
struct PartialSheet<Content: View>: View {
var sheetSize: SheetSize
let content: () -> Content
init(_ sheetSize: SheetSize, @ViewBuilder content: @escaping () -> Content) {
self.sheetSize = sheetSize
self.content = content
}
var body: some View {
content()
.frame(height: sheetHeight())
.animation(.spring(), value: sheetSize)
}
private func sheetHeight() -> CGFloat {
switch sheetSize {
case .quarter:
return UIScreen.main.bounds.height / 4
case .half:
return UIScreen.main.bounds.height / 2
case .threeQuarter:
return UIScreen.main.bounds.height * 3 / 4
case .full:
return .infinity
}
}
}
enum SheetSize: Equatable {
case quarter, half, threeQuarter, full
}
Use it like this in SplitViewFormPicker:
NavigationView {
VStack {
List {
// The same code as above not repeated to save space
}
.listStyle(PlainListStyle())
// Call PartialSheet, tell it what size you want, and pass the view as a closure.
// It will return that view sized to the screen.
PartialSheet(.half) {
IncidentForm()
}
}
}
}
You may notice that because the views are in a VStack
, that the biggest the IncidentForm
will get is half of the screen. However, if you put it in a ZStack
it will limit itself as you requested. Use it how you need.