Smart separators between self-hiding views - swiftui

I have a view that encapsulates some presentation logic, and as part of that logic, it can hide itself. A toy example:
struct Item: View {
var x: Int
var body: some View {
if x % 3 == 1 {
return AnyView(EmptyView())
}
return AnyView(Text("\(x)").background(Color.blue))
}
}
When I'm using my Item's in a VStack, it is smart enough to insert spacing only between non-empty ones.
VStack(spacing: 8) {
Item(x: 0)
Item(x: 1)
Item(x: 2)
Item(x: 3)
}
Now I want to do the same, but using a custom separator instead of spacing. Similarly, I want separator to be inserted only between non-empty items.
Is there an API that would insert 2 separators between 3 visible views? Something like this:
Something(separator: Divider()) {
Item(x: 0)
Item(x: 1)
Item(x: 2)
Item(x: 3)
}
I've checked VStack, Group, ForEach but didn't find anything. I really don't want to lift the hiding logic up into the parent. Any ideas for the workaround that would keep the hiding logic inside the Item?

Here is possible approach (and you continue use same VStack)
Tested with Xcode 12 / iOS 14
struct Item: View {
var x: Int
var body: some View {
if x % 3 == 1 {
return AnyView(EmptyView())
}
return AnyView(
VStack {
Text("\(x)").background(Color.blue)
Divider().padding(.horizontal)
}
)
}
}

Related

SwiftUI - Ternary operator on padding modifier crashes program

I've got a ZStack in SwiftUI filled with some components delivered by a ForEach, as follows:
ForEach(0..<arr.count) { i in
ZStack {
...
}
// I use i later in this code
...
}
The program runs perfectly like this.
But I want to add padding to the ZStack only if i == 0, so I tried adding this modifier to the ZStack: .padding(.top, i == 0 ? 70 : 0)
When I try to build it with this modifier it fails, but doesn't even say "build failed." It takes about 5 minutes attempting to build (when it usually takes 5 seconds) then decides to crash. Can anyone explain why this is happening and how I can get this conditional padding without breaking my program?
Try this:
ForEach(0..<arr.count) { i in
ZStack {
...
}
.padding(.top, getPadding(i))
// I use i later in this code
...
}
func getPadding(_ i: Int) -> CGFloat {
if i == 0 {
return CGFloat(70)
}
return CGFloat(0)
}
func setPadding(_ statement: String) -> CGFloat {
if statement == " Put your statement " {
return CGFloat(your requirement padding)
} else {
return CGFloat("According to your requirement padding")
}

Changing swipeActions dynamically in SwiftUI

I am trying to change the swipeAction from "Paid" to "UnPaid" based on payment status and somehow seems to be failing. Error: "The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions"
Appreciate any help
struct ContentView: View {
var data: [Data] = [data1, data2, data3, data4]
#State var swipeLabel = true
var body: some View {
let grouped = groupByDate(data)
List {
ForEach(Array(grouped.keys).sorted(by: >), id: \.self) { date in
let studentsDateWise = grouped[date]!
Section(header:Text(date, style: .date)) {
ForEach(studentsDateWise, id:\.self) { item in
HStack {
Text(item.name)
padding()
Text(item.date, style: .time)
if(item.paymentStatus == false) {
Image(systemName: "person.fill.questionmark")
.foregroundColor(Color.red)
} else {
Image(systemName: "banknote")
.foregroundColor(Color.green)
}
} // HStack ends here
.swipeActions() {
if(item.paymentStatus) {
Button("Paid"){}
} else {
Button("UnPaid"){}
}
}
} // ForEach ends here...
} // section ends here
} // ForEach ends here
} // List ends here
} // var ends here
}
The body func shouldn't do any grouping or sorting. You need to prepare your data first into properties and read from those in body, e.g. in an onAppear block. Also if your Data is a struct you can't use id: \.self you need to either specify a unique identifier property on the data id:\.myUniqueID or implement the Indentifiable protocol by either having an id property or an id getter that computes a unique identifier from other properties.
I would suggest separating all this code into small Views with a small body that only uses one or a two properties. Work from bottom up. Then eventually with one View works on an array of dates and another on an array of items that contains the small Views made earlier.
You should probably also learn that if and foreach in body are not like normal code, those are converted into special Views. Worth watching Apple's video Demystify SwiftUI to learn about structural identity.

SwiftUI - passing an array to a function

I'm trying to create a simple app that keeps count of how buttons in a list of clothing items have received more than 5 taps.
I've tried using the code below, but keep getting the error "Cannot convert value of type 'ContentView.clothing' to expected argument type 'Int'" in the line if clothes[i].taps >= 5.
I feel like somehow this might be related to the fact that I've tried to pass a whole struct as a parameter of the func. Perhaps I'm not meant to do this?
Any guidance much appreciated!
import SwiftUI
struct ContentView: View {
struct clothing {
var type: String
var taps: Int
}
#State var currentClothes = [
clothing(type: "tshirt", taps: 0),
clothing(type: "dress", taps: 0)
]
var body: some View {
NavigationView{
List{
ForEach(0..<currentClothes.count) { i in
Button("\(currentClothes[i].type)") {
self.currentClothes[i].taps += 1
print(currentClothes)
}
}
}
Text("\(tapCount(clothes: currentClothes))")
}
}
func tapCount(clothes: [clothing]) -> Int {
var total = 0
for i in clothes {
if clothes[i].taps >= 5
{total += 1}
}
return total
}
}
It should be
for i in clothes {
if i.taps >= 5
{total += 1}
}
not
for i in clothes {
if clothes[i].taps >= 5
{total += 1}
}
because you are looping over clothes, not clothes.count or something. i is a clothing, not an Int.
As a side note, try to keep your classes and structs Uppercased. clothing would be better off as Clothing.

SwiftUI - ForEach with Stride

Im trying to create a list of Hstack'd cards, That is to say, I want to create a scroll view of a series of rows. Each row would contain an HStack of two views displayed side by side, and initialized by some list data structure.
struct MyHStackView: View {
var myArray = [SomeStruct(1), SomeStruct(3), SomeStruct(4), SomeStruct(5), SomeStruct(6)]
var body: some View {
ScrollView(.vertical) {
VStack {
ForEach(0..<self.myArray.count) { index in
HStack {
SubView(myArray[index])
SubView(myArray[index+1])
}
}
}
}
The only issue is my current implementation touches every element of the array, is there a stride function built into the new ForEach so that I can index on every other element on the array to initialize the row? How would you approach this?
If just every other, you may try
VStack {
ForEach(0 ..< self.myArray.count/2) { index in
HStack {
SubView(myArray[index * 2])
SubView(myArray[index * 2 + 1])
}
}
}
Otherwise, you may need to use the stride function:
ForEach(Array(stride(from: 0, to: self.myArray.count, by: 2)), id: \.self) { index in
// ...
}
For simple use-cases the solution posted by E. Coms may work. If you plan on modifying or re-ordering the list, it may give you trouble since the id specified is the same as the index and List won't be able to correctly animate removals/additions/re-ordering.
I would create a data-structure to represent the tuple in an identifiable manner:
struct SomeStructTouple: Identifiable {
let a: SomeStruct
let b: SomeStruct
var id: String {
"\(a.id)-\(b.id)"
}
}
and then create an array of touples to generate the list.

SwiftUI: how to remove spacing around Text()?

As example I have 3 properties:
var path1FilePath:String = "Src/"
var path2FileName: String = "filename"
var path3Extension: String = ".jpg"
I need to display them with the following way:
HStack {
Text(status.path1FilePath)
Text(status.path2FileName).bold()
Text(status.path3Extension)
}
problem is spacing between Text() views. How to remove them?
SwiftUI allows us to combine strings together like Text("Hello ") + Text("World!"), so you can do the same here:
Text(path1FilePath)
+ Text(path2FileName)
+ Text(path3Extension)
Alternatively, if you still want or need to use an HStack, just use HStack(spacing: 0) and you'll get the same result.
There are 2 ways:
Solution 1:
Text(path1FilePath)
+ Text(path2FileName)
+ Text(path3Extension)
but in this way you cannot apply modifiers =(
Solution 2:
HStack (spacing: 0) {
Text(path1FilePath)
Text(path2FileName)
.bold()
Text(path3Extension)
}
.someTextModifier()