SwiftUI - passing an array to a function - swiftui

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.

Related

In SwiftUI, how to create a non View object only once inside a child View

I want to link to a view that contains a non-view object - created once per user tap of the "Start" link - that is dependent on data selected by the user. The code below is as close as I've gotten. QuestionView.init is called as soon as HomeView appears, and again every time you select a new value for Highest Number, thus creating the Question object repeatedly, which is what I'm trying to avoid. I want to only create the Question object one time - when the user taps on the "Start" link.
I've tried many different approaches. It feels like I am stuck problem solving from an imperative UI oriented approach, instead of the new-for-me declarative approach of SwiftUI. Perhaps there's a bridge I'm missing from the state-driven approach of Views to the more familiar-to-me non-view objects, like my Question object I want to create only once.
struct Question {
let value1: Int
let value2: Int
let answer: Int
init(_ highestNumber: Int) {
print("Question.init \(highestNumber)")
value1 = Int.random(in: 1...highestNumber)
value2 = Int.random(in: 1...highestNumber)
answer = value1 * value2
}
var prompt: String {
"\(value1) x \(value2) = ?"
}
}
struct HomeView: View {
#State var highestNumber: Int = 12
var body: some View {
NavigationStack {
Picker("Highest Number", selection: $highestNumber) {
ForEach(4...12, id: \.self) { Text(String($0)) }
}
.pickerStyle(.wheel)
NavigationLink(destination: QuestionView(highestNumber: $highestNumber)) {
Text("Start")
}
}
}
}
struct QuestionView: View {
#Binding var highestNumber: Int
#State var question: Question
init(highestNumber: Binding<Int>) {
print("QuestionView.init")
self._highestNumber = highestNumber
question = Question(highestNumber.wrappedValue)
}
var body: some View {
Text(question.prompt)
Button("I got it") {
question = Question(highestNumber)
}
}
}

How to get a row count in SwiftUI List

I am trying to display the number of rows in a section in its header as shown below as COUNTHERE. The issue I'm running into is that I can't put any code inside the if statement that is not a view so I can't compute anything. Ideas?
struct Day1View: View {
var displayEmployees: [Employee]
var body: some View {
List {
Section(header: Text("Early (\(COUNTHERE)")) {
ForEach(displayEmployees) { employee in
if employee.shift == .early {
switch employee.post {
case .kitchen : Text(employee.name).foregroundColor(.blue)
case .floor : Text(employee.name).foregroundColor(.yellow)
case .upstairs : Text(employee.name).foregroundColor(.red)
case .greeting : Text(employee.name).foregroundColor(.green)
default : Text(employee.name).foregroundColor(.gray)
}
}
}
}
}
Since the section you showed is only for .early shift employees, you can get the count using a filtered version of the original array:
displayEmployees.filter({$0.shift == .early}).count
So your section becomes:
Section(header: Text("Early (\(displayEmployees.filter({$0.shift == .early}).count)")) {
Or, you can add a new computed property for the count:
var displayCount: Int {
return displayEmployees.filter({$0.shift == .early}).count
}
...
Section(header: Text("Early (\(displayCount)")) {

Code for textfield character limit isn't working(SwiftUI)

I've stumbled across this piece of code:
class TextLimiter: ObservableObject {
private let limit: Int
init(limit: Int) {
self.limit = limit
}
#Published var value = "" {
didSet {
if value.count > self.limit {
value = String(value.prefix(self.limit))
self.hasReachedLimit = true
} else {
self.hasReachedLimit = false
}
}
}
#Published var hasReachedLimit = false }
struct Strix: View {
#ObservedObject var input = TextLimiter(limit: 5)
var body: some View {
TextField("Text Input",
text: $input.value)
.border(Color.red,
width: $input.hasReachedLimit.wrappedValue ? 1 : 0 )
} }
It's a TextField limiting code where after a user inputs characters after a limit, it won't keep inputing characters inside the box. I've tried this code and after the limit is reached, it just keeps on inputting characters.
For example:
How it's supposed to work: limit is 5 so the only input allowed is 'aaaaa'
How it's behaving: limit is 5 but input allowed is 'aaaaaaaa.....'
I'm aware of a recent solution to this:
How to set textfield character limit SwiftUI?
but the solution is specifically tailored for iOS 14. I was hoping to be able to support iOS 13. Thanks.
Link to original code:
https://github.com/programmingwithswift/SwiftUITextFieldLimit/blob/master/SwiftUITextFieldLimit/SwiftUITextFieldLimit/ContentView.swift
Your solution is lies in SwiftUI's subscriber .onReceive,
Make sure that your property hasReachedLimit must not marked with #Published else it will trigger infinite loop of view body rendering.
Below shown code works as your expectation.
class TextLimiter: ObservableObject {
let limit: Int
#Published var value = ""
var hasReachedLimit = false
init(limit: Int) {
self.limit = limit
}
}
struct Strix: View {
#ObservedObject var input = TextLimiter(limit: 5)
var body: some View {
TextField("Text Input",
text: $input.value)
.border(Color.red,
width: $input.hasReachedLimit.wrappedValue ? 1 : 0 )
.onReceive(Just(self.input.value)) { inputValue in
self.input.hasReachedLimit = inputValue.count > self.input.limit
if inputValue.count > self.input.limit {
self.input.value.removeLast()
}
}
}
}
BTW this is not an efficient solution.

Smart separators between self-hiding views

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)
}
)
}
}

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.