I am making a UI to change the 3D coordinates of an object, and I thought it would make sense to put all three on the same line with a label beforehand, sort of like System Preferences does for number separators :
However, doing so messes up the alignment of the whole form, and I'm not sure how to resolve this (except by adding VStacks and HStacks everywhere, which I really hope is not the best available solution) :
Here is the code driving the view :
struct ObjectSettingsView: View {
#State var object : Object
var body : some View {
Form {
TextField("Name:", text: $object.name, prompt : Text("New Object"))
Toggle(
"Visible",
isOn: $object.visible
)
Divider()
HStack {
Text("Coordinates:")
NumberView(label : "X:", number : object.coordinates.x)
NumberView(label : "Y:", number : object.coordinates.y)
NumberView(label : "Z:", number : object.coordinates.z)
}
}
}
}
struct NumberView : View{
var label : String
#State var number : Int32
var body : some View {
HStack {
TextField(
self.label,
value: self.$number,
formatter: NumberFormatter()
)
Stepper("", value: self.$number, in: 1...8)
.labelsHidden()
}
}
}
( I know this really should be using a ViewModel, I'm just trying to figure out how forms work right now )
I add #Binding and LazyVGrid to your Code.
Maybe this helps:
struct ObjectData {
var name: String = "New Object"
var visible: Bool = true
var coordinates_x: Int32 = 0
var coordinates_y: Int32 = 0
var coordinates_z: Int32 = 0
}
struct ContentView: View {
#State var data = ObjectData()
let columns = [
GridItem(alignment: .trailing),
GridItem(alignment: .leading),
]
var form: some View {
LazyVGrid(columns: columns) {
Text("Name:")
TextField("", text: $data.name)
Text("blind").opacity(0)
Toggle("Visible:", isOn: $data.visible)
Text("Coordinates:")
HStack {
NumberView(label : "X:", number : $data.coordinates_x)
NumberView(label : "Y:", number : $data.coordinates_y)
NumberView(label : "Z:", number : $data.coordinates_z)
}
}
}
var body : some View {
VStack() {
form
Text(" --- Check --- ")
Text(String(describing: data))
}
.frame(width: 400.0)
}
}
struct NumberView : View{
var label : String
#Binding var number : Int32
var body : some View {
HStack {
TextField(
self.label,
value: self.$number,
formatter: NumberFormatter()
)
Stepper("", value: self.$number, in: 1...8)
.labelsHidden()
}
}
}
Separating things into two Forms almost does the trick, although labels are still not exactly aligned as in system Preferences :
struct ObjectSettingsView: View {
#State var object : Object
var body : some View {
VStack {
Form {
TextField("Name:", text: $object.name, prompt : Text("New Object"))
Toggle("Visible", isOn: $object.visible)
}
Divider()
Form {
HStack {
Text("Coordinates:")
NumberView(label : "X:", number : object.coordinates.x)
NumberView(label : "Y:", number : object.coordinates.y)
NumberView(label : "Z:", number : object.coordinates.z)
}
}
}
.padding()
}
}
// static let left = GridItem.Size.flexible(minimum: 10, maximum: .infinity)
static let left = GridItem.Size.flexible(minimum: 40, maximum: 100)
static let right = GridItem.Size.flexible(minimum: 40, maximum: 200)
let columns = [
GridItem(left, alignment: .trailing),
GridItem(right, alignment: .leading),
]
The problem is that the size structs are set to .infinity
I changed the column settings like above with fixed max values.
To get all infos see Apple Docs about GridItem.
Related
I have a ListView that has 20 items and uses the following Image within the ForEach loop:
Image(systemName: "heart").foregroundColor(.red).onTapGesture {
selected.toggle()
favLists.append(country.id)
favLists = favLists.removingDuplicates()
}
.scaleEffect(self.selected ? 1.5 : 1)
The issue is that selected is a single variable, so that is toggling the state for ALL my items in the list. How can I declare dynamic state dependent on the number of index items?
try this approach, ...to scale individual image within a ForEach loop, works for me:
struct ContentView: View {
#State var selected = "heart"
var body: some View {
VStack (spacing: 55) {
ForEach(["globe", "house", "person", "heart"], id: \.self) { name in
Image(systemName: name).id(name)
.onTapGesture {
selected = name
// ....other code
}
.foregroundColor(selected == name ? .red : .blue)
.scaleEffect(selected == name ? 2.5 : 1)
}
}
}
}
Im trying to get sort id which is coming from my server . I can get UUID for each sort type in menu picker but how can I get their id instead of UUID when I select a sort type in Menu.As you can see in my code I can get UUID with Just(selection) but I need to get sort id which is data.id .
#State private var selection = UUID()
#Binding var sortClicked : Bool
#ObservedObject var productListViewModel = ProductListViewModel()
var sortListArray : [ProductListSortAndFilterList]
var body : some View {
HStack(alignment: .center){
Spacer()
Picker(selection: $selection, label: SortView().frame(height:UIScreen.main.bounds.height * 0.050), content: {
ForEach(sortListArray, id: \.uid, content: { item in
if item.id == "sort" {
ForEach(item.sortList ?? [],id:\.uid) { data in
Text(data.name ?? "")
.tag(data.id)
}
.onReceive(Just(selection)) { (value) in
print(value)
}
}
})
})
.pickerStyle(MenuPickerStyle())
.padding()
.background(Color(.systemBackground).edgesIgnoringSafeArea(.all))
.cornerRadius(5)
I want the SF Images to automatically go to the next line. I understand that HStack would keep content on the same line but, I am not able to come up with a solution on preventing it from going off screen. I have also tried using geometry reader but that also doesn't work.
https://i.stack.imgur.com/nLUzb.png
struct SplitTextView: View {
static let input = "B, LB, Y, RT, A, X, B, Right, X, LB, LB, LB"
let letters = input.components(separatedBy: ", ")
var body: some View {
GeometryReader { geo in
HStack (spacing: 10) {
ForEach(0..<self.letters.count) { index in
ButtonGeneratorView(buttonKey: self.letters[index])
}
}.frame(width: geo.size.width/2)
}
}
}
struct ButtonGeneratorView: View {
let buttonKey: String
let color: [String : Color] = ["Y" : Color.yellow,
"B" : Color.red,
"A" : Color.green,
"X" : Color.blue
]
var body: some View {
VStack {
if buttonKey.count == 1 {
Image(systemName: "\(buttonKey.lowercased()).circle.fill")
.font(.system(size: 32))
.foregroundColor(color["\(buttonKey)"])
}
else {
Image(systemName: "questionmark.circle.fill")//SF images for unknown
.font(.system(size: 32))
}
}
}
}
SwiftUI 2.0
Use LazyVGrid as in demo below
struct SplitTextView: View {
static let input = "B, LB, Y, RT, A, X, B, Right, X, LB, LB, LB"
let letters = input.components(separatedBy: ", ")
let layout = [
GridItem(.adaptive(minimum:32), spacing: 10)
]
var body: some View {
LazyVGrid(columns: layout, spacing: 10){
ForEach(0..<self.letters.count) { index in
ButtonGeneratorView(buttonKey: self.letters[index])
}
}
}
}
SwiftUI 1.0
There is no native built-in feature for this. The solution for similar problem I provided in SwiftUI HStack with wrap and dynamic height, and it can be adapted for this task as well.
using core data im storing some airport and for every airport i'm storing different note
I have created the entity Airport and the entity Briefing
Airport have 1 attribute called icaoAPT and Briefing have 4 attribute category, descript, icaoAPT, noteID
On my detailsView I show the list all the noted related to that airport, I managed to have a dynamic fetch via another view called FilterList
import SwiftUI
import CoreData
struct FilterLIst: View {
var fetchRequest: FetchRequest<Briefing>
#Environment(\.managedObjectContext) var dbContext
init(filter: String) {
fetchRequest = FetchRequest<Briefing>(entity: Briefing.entity(), sortDescriptors: [], predicate: NSPredicate(format: "airportRel.icaoAPT == %#", filter))
}
func update(_ result : FetchedResults<Briefing>) ->[[Briefing]]{
return Dictionary(grouping: result) { (sequence : Briefing) in
sequence.category
}.values.map{$0}
}
var body: some View {
List{
ForEach(update(self.fetchRequest.wrappedValue), id: \.self) { (section : Briefing) in
Section(header: Text(section.category!)) {
ForEach(section, id: \.self) { note in
Text("hello")
/// Xcode error Cannot convert value of type 'Text' to closure result type '_'
}
}
}
}
}
}
on this view I'm try to display all the section divided by category using the func update...
but Xcode give me this error , I can't understand why..Cannot convert value of type 'Text' to closure result type '_'
fore reference I list below my detailsView
import SwiftUI
struct DeatailsView: View {
#Environment(\.managedObjectContext) var dbContext
#Environment(\.presentationMode) var presentation
#State var airport : Airport
#State var note = ""
#State var noteTitle = ["SAFTY NOTE", "TAXI NOTE", "CPNOTE"]
#State var notaTitleSelected : Int = 0
#State var notaID = ""
var body: some View {
Form{
Section(header: Text("ADD NOTE Section")) {
TextField("notaID", text: self.$notaID)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
TextField("add Note descrip", text: self.$note)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
Picker(selection: $notaTitleSelected, label: Text("Class of Note")) {
ForEach(0 ..< noteTitle.count) {
Text(self.noteTitle[$0])
}
}
HStack{
Spacer()
Button(action: {
let nota = Briefing(context: self.dbContext)
nota.airportRel = self.airport
nota.icaoAPT = self.airport.icaoAPT
nota.descript = self.note
nota.category = self.noteTitle[self.notaTitleSelected]
nota.noteID = self.notaID
do {
try self.dbContext.save()
debugPrint("salvato notazione")
} catch {
print("errore nel salva")
}
}) {
Text("Salva NOTA")
}
Spacer()
}
}
Section(header: Text("View Note")) {
FilterLIst(filter: airport.icaoAPT ?? "NA")
}
}
}
}
thanks for the help
This is because you try to iterate over a single Briefing object and a ForEach loop expects a collection:
List {
ForEach(update(self.fetchRequest.wrappedValue), id: \.self) { (section: Briefing) in
Section(header: Text(section.category!)) {
ForEach(section, id: \.self) { note in // <- section is a single object
Text("hello")
/// Xcode error Cannot convert value of type 'Text' to closure result type '_'
}
}
}
}
I'd recommend you to extract the second ForEach to another method for clarity. This way you can also be sure you're passing the argument of right type ([Briefing]):
func categoryView(section: [Briefing]) -> some View {
ForEach(section, id: \.self) { briefing in
Text("hello")
}
}
Note that the result of your update method is of type [[Briefing]], which means the parameter in the ForEach is section: [Briefing] (and not Briefing):
var body: some View {
let data: [[Briefing]] = update(self.fetchRequest.wrappedValue)
return List {
ForEach(data, id: \.self) { (section: [Briefing]) in
Section(header: Text("")) { // <- can't be `section.category!`
self.categoryView(section: section)
}
}
}
}
This also means you can't write section.category! in the header as the section is an array.
You may need to access a Briefing object to get a category:
Text(section[0].category!)
(if you're sure the first element exists).
For clarity I specified types explicitly. It's also a good way to be sure you always use the right type.
let data: [[Briefing]] = update(self.fetchRequest.wrappedValue)
However, Swift can infer types automatically. In the example below, the data will be of type [[Briefing]]:
let data = update(self.fetchRequest.wrappedValue)
I'm working on my first Swift/SwiftUI project (other than the tutorials) and have run into what I think is a common problem -- the error Generic parameter 'Parent' could not be inferred -- toward the top of the view when the problem is actually with a List I'm trying to generate lower down in the form.
The app I'm building is a simple invoicing app: the user fills out the form fields and sends the invoice. An invoice can have multiple line items that the user enters one at a time and then are appended to a dictionary that should display inside the form.
This is the relevant variables from the top of the struct and the beginning of the view, where I hope I'm declaring the variables for line items correctly to modify them based on user input.
*Edited following #asperi's advice below.
#State private var lineItems = [LineItem]()
#State private var lineItem = LineItem()
struct LineItem: Codable, Hashable {
var productSku: String = ""
var productName: String = ""
var quantity: Double = 0
var unitPrice: Double = 0
}
func addLineItem(lineItem: LineItem){
lineItems.append(lineItem)
}
...
var body: some View {
NavigationView {
Form {
Section(header: Text("Customer Information")) { <-- error appears here
TextField("Customer Name", text: $customerName)
TextField("Customer Email", text: $customerEmail)
}
Here's the relevant portion of the form, where I'm trying to list all current line items and then allow the user to insert additional line items. I don't get any error until I add the List code, so I'm pretty sure I'm doing something wrong there.
Section(header: Text("Items")) {
List(lineItems, id: \.self) { item in
LineItemRow(lineItem: item)
Text(item.productName)
}
TextField("Product SKU", text: $productSKU)
TextField("Poduct Name", text: $productName)
TextField("Unit Price", text: $unitPrice, formatter: DoubleFormatter())
Picker("Quantity", selection: $quantity) {
ForEach(0 ..< 10) {
Text("\($0)")
}
}
Button(action: {
self.addLineItem(lineItem: LineItem(productSku:$productSKU,
productName:$productName,
quantity:$unitPrice,
unitPrice:$quantity))
print($lineItems)
}, label: {
Text("Add line item")
})
}
I've tested the button functionality in the console and it does seem to be appending to the dictionary correctly, but I need it to display as well.
I'm probably getting something very basic wrong with the List. Any advice?
For reference, here's the whole view:
struct AddInvoiceForm: View {
#State private var invoiceNumber: String = ""
#State private var _description: String = ""
#State private var dueDate: Date = Date()
#State private var sendImmediately: Bool = true
#State private var currency = 0
#State private var paymentType = 0
#State private var customerName: String = ""
#State private var customerEmail: String = ""
#State private var productSKU: String = ""
#State private var productName: String = ""
#State private var quantity: Int = 0
#State private var unitPrice: String = ""
#State private var lineItems = [LineItem]()
#State private var lineItem = LineItem()
struct LineItem: Codable, Hashable {
var productSku: String = ""
var productName: String = ""
var quantity: Double = 0
var unitPrice: Double = 0
}
func addLineItem(lineItem: LineItem){
lineItems.append(lineItem)
}
#State private var totalAmount: Double = 0.0
static let currencies = ["USD","GBP"]
var body: some View {
NavigationView {
Form {
Section(header: Text("Customer Information")) {
TextField("Customer Name", text: $customerName)
TextField("Customer Email", text: $customerEmail)
}
Section(header: Text("Invoice Information")) {
TextField("Invoice Number", text:$invoiceNumber)
TextField("Description", text:$_description)
DatePicker(selection: $dueDate, in: Date()..., displayedComponents: .date) {
Text("Due date")
}
Picker("Currency", selection: $currency) {
ForEach(0 ..< Self.currencies.count) {
Text(Self.currencies[$0])
}
}
}
Section(header: Text("Items")) {
List(lineItems, id: \.self) { item in
LineItemRow(lineItem: item)
Text(item.productName)
}
TextField("Product SKU", text: $productSKU)
TextField("Poduct Name", text: $productName)
TextField("Unit Price", text: $unitPrice, formatter: DoubleFormatter())
Picker("Quantity", selection: $quantity) {
ForEach(0 ..< 10) {
Text("\($0)")
}
}
Button(action: {
self.addLineItem(lineItem: LineItem(productSku:$productSKU,
productName:$productName,
quantity:$unitPrice,
unitPrice:$quantity))
print($lineItems)
}, label: {
Text("Add line item")
})
}
Section(header: Text("Totals")) {
Text("\(totalAmount)")
}
Section {
Button(action: {
print(Text("Send"))
}, label: {
Text("Send invoice")
})
}
}.navigationBarTitle("Invoice Details")
}
}
}
List(lineItems, id: \.productSku) { item in <-- I get the error when I add this
Your item is dictionary, but dictionary does not have .productSku key path, so the error.
I assume most simple & correct would be to make Item as struct
struct LineItem {
var productSku: String
...
}
...
#State private var lineItems = [LineItem]()
#State private var lineItem = LineItem()
...
List(lineItems, id: \.productSku) { item in