How can I combine text views by looping? - swift

We can create new text views out of several small ones using +, which is an easy way of creating more advanced formatting. For example, this creates three text views in different colors and combines them together:
struct ContentView: View {
var body: some View {
Text("Colored ")
.foregroundColor(.red)
+
Text("SwifUI ")
.foregroundColor(.green)
+
Text("Text")
.foregroundColor(.blue)
}
}
But how can I create combined text views by looping through. For example (which doesn't work)
View v;
ForEach((1...3), id: \.self) {
v.append(Text("\($0)"))
}

You are essentially trying to get an expression of type Text, from a sequence 0...3. You can first map the sequence to Text objects. How do you combine a sequence of objects to a single one? reduce!
(1...3).map { Text("\($0)") }.reduce(Text(""), +)
Note that Text("") acts like the identity element of the + operation.

Related

How do we control the automatic dividers in SwiftUI forms?

I'm curious, is there a way we can control (or make sense of) the automatic dividers that swiftui puts between items?
Here's some code showing strange behaviour (on iOS simulator):
struct FormSeparator: View {
#State private var passwd = ""
var body: some View {
Form {
ZStack(alignment: .trailing) {
TextField("password", text:$passwd)
Text("8 chars min")
.font(.caption)
.foregroundColor(.secondary)
}
TextField("confirm", text:$passwd)
}
}
}
Here's the result:
This seems to have something to do with the Text that I'm stacking on top of the password field. If I change it the Text to Image(systemName: "lock") then everything works as you'd expect:
Mind you, I don't even understand why it doesn't extend all the way across the form in that case. Presumably this is deliberate, has anyone seen anything official about this situation? Do we know how to control this?
In iOS 16, List/Form row separator insets automatically and aligns with the text. In iOS 16, we got two new alignments, listRowSeparatorLeading and listRowSeparatorTrailing, to work with alignmentGuide(_:computeValue:).
ZStack(alignment: .trailing) {
TextField("password", text:$passwd)
Text("8 chars min")
.font(.caption)
.foregroundColor(.secondary)
}
.alignmentGuide(.listRowSeparatorLeading) { viewDimensions in
return 0
}

why is the swiftUI list not lazy (compared to lazyVStack)?

are lists in SwiftUI really lazy?
I am having a list of 5000 elements fetched from coreData and I want to display them in a list. I read a lot of comments (like that: https://developer.apple.com/forums/thread/651256) that Lists are lazy but for me it looks like they are not.... Loading the list takes 15 seconds.
List(element, id: \.objectID, selection: $selection) { file in
RowView(file)
}
If I use a ScrollView + LazyVStack + ForEach instead the loading takes less than a second.
ScrollView {
LazyVStack(alignment: .leading, spacing: 0) {
ForEach(element, id: \.objectID) { file in
RowView(file)
.onTapGesture {
selection = Set([file.objectID])
}
}
}
}
I would prefer taking a list to make use of the functionalities it provides.
Am I doing something wrong or is list not lazy?
Thanks for help!
(i am working with SwiftUI (for Mac)).

Animate Change in SwiftUI ForEach Grid Using Array Indices

Does anyone know how I can animate a sorting change to grid items while iterating off of the array index in a ForEach?
I am showing a grid of items (LazyVGrid) from an array and the user can change the sort order of these items. When the sort order changes, I'd like to animate the change in the grid. This would work great if I were to use code similar to the following:
Button {
withAnimation {
noteModel.sortMethod = noteModel.sortMethod.next()
}
} label: {
Text("Change Sort Order")
}
ScrollView {
LazyVGrid(columns: columns) {
ForEach(self.notes, id: \.self) { note in
VStack {
Text(note.title)
Text(note.body)
}
.padding()
}
}
}
However, if my ForEach iterates off of the array index the change does not animate. So it won't animate if my code is similar to this:
Button {
withAnimation {
noteModel.sortMethod = noteModel.sortMethod.next()
}
} label: {
Text("Change Sort Order")
}
ScrollView {
LazyVGrid(columns: columns) {
ForEach(self.notes.indices, id: \.self) { idx in
VStack {
Text(self.notes[idx].title)
Text(self.notes[idx].body)
}
.padding()
}
}
}
Why don't I just do it the first way? The reason is because I found a great resource for dynamically adjusting frame sizes for items inside a grid (found here: https://swiftui-lab.com/impossible-grids/), but that requires the use of array indices in order to work. It's pretty cool and I am hoping I don't have to choose one or the other (dynamic sizing or animate changes).
Thoughts or ideas would be greatly appreciated. Thanks!
ForEach in SwiftUI isn’t the same as a for loop. Your data needs to be identifiable for structural identity to work and you certainly should not be using indices. Either implement the Identifiable protocol or tell ForEach what key path is a unique ID (or is a getter that creates one from other properties) in the item struct. Don’t ever use id:\.self.
You can learn more about structural identity in the video WWDC 2021 Demystify SwiftUI

Swift List looks too big

I have a List in my App. When I click on an Item in the List it opens another List. The First List looks fine, but the second List looks too big. Here is how it looks normal(good):
And here is the List that looks not good:
On the second Picture, the Problems I have are:
the title is too big
the list item starts too far away from the title
Thats the Code, the Code looks the same in both Lists. The only difference is that I used some different variable names in the second one.
return VStack {
ZStack {
NavigationView {
List {
ForEach(zettelArr) { x in
NavigationLink(destination: ZettelViewDetails(passedVar: x)) {
Text("\(x.name)")
}
}
}.navigationBarTitle(alertVariable)
.navigationBarItems(trailing: Button(action: {
alertView()
//self.isShown = true
}) {
Image(systemName: "plus")
})
}
//SwiftUIAlertViewWithTextBox(isShown: $isShown, message: $msg, title: $title)
}
}
My goal is, that the second List looks exactly like the Main List.
How is this possible ?
Okay I don't know why the formatting from the pictures are not working, maybe I am just dumb. The first Picture is the MainList that looks like I want it to look and the second one is the list that is formatted differently, even though its quite the same Code.
You are using NavigationView for both lists view , while only the first one should be inside a NavigationView
Your ZettelViewDetails shouldn't have NavigationView as parent
Example :
Main
NavigationView {
List {
ForEach(zettelArr) { x in
NavigationLink(destination: ZettelViewDetails(passedVar: x))
{
Text("\(x.name)")
}
}
}.navigationBarTitle(alertVariable)
}
Details
VStack {
ForEach(zettelArr) { x in
NavigationLink(destination: ZettelViewDetails(passedVar: x)) {
Text("\(x.name)")
}
}
}

SwiftUI does not perform onInsert when the list is empty?

I have 2 lists on the UI, 1 list is allow user to drag to another list. I have problem when the
"selected product list" is empty. It does not perform the .onInsert function.
It have no problem when the array "selectedProduct" is not empty when it was rendering.
How can i to allow user to drag and drop when the other list is completely empty?
List(selection: $selection)
{
HStack(alignment: .center)
{
Spacer().frame(width: 200)
Text("Product Name")
.foregroundColor(blue)
.lineLimit(1)
Spacer()
Text("Price")
.foregroundColor(blue)
Spacer()
Text("Unit")
.foregroundColor(blue)
Spacer()
}
ForEach(selectedProduct, id:\.id)
{
rider in
SelectedProductView()
}
.onInsert(of: ["public.data"])
{
self.addProduct(position: $0, itemProviders: $1, top: true)
}
}
The .onInsert works on DynamicViewContent, so when your selectedProduct is empty there is no content, so nowhere to insert.
There two possibilities as I see to solve this situation:
1) implement support for drop on List and make it active when selectedProduct is empty to make possible to add first element in dynamic content
2) implement stub product-item (like, empty row showing "drop here" text) that is explicitly added to selectedProduct when it becomes empty of real products, thus making content not empty to allow standard .onInsert work for you.