So I'm iterating over items in an array, I wish to display an ad say after every 3 or if the current index is divisble/a multiple of 3.
if !homeViewModel.posts.isEmpty {
ForEach(homeViewModel.posts, id: \.postID) { post in
HeaderCell(post: post)
GADNativeViewControllerWrapper()
.frame(height: 250)
.padding(10)
}
Yes, I am aware I will be showing an ad after every cell but I have tried iterating over the .indicies which has more of less given me the same result. How do I get the curreny index of post in and check it's position in the array?
I'm just lost here right now, any help would be appreciated.
Thank you.
To display an ad every 3rd element in an array you need to iterate over the .indices and conditionally display content based on the current index.
struct ContentView: View {
let posts = [1,2,3,4, 5, 6, 7, 8, 9, 10]
var body: some View {
VStack {
ForEach(posts.indices, id: \.self) { index in
HStack {
Text("Index: \(index)").bold()
Text("Post: \(posts[index])")
}
if (index + 1) % 3 == 0 {
Rectangle().fill(Color.blue)
.frame(height: 30)
}
}
}
}
}
Result:
Related
I have a dynamic array of Strings, that I want to display in a 2-column format. This is my first stab at it:
ForEach(0..<theWords.count, id: \.self) { ind in
HStack {
if ind.isMultiple(of: 2) {
Text(theWords[ind])
.offset(x: -65, y: 0)
}
if !ind.isMultiple(of: 2) {
Text(theWords[ind])
.offset(x: 65, y: -24)
}
}
}
But very inelegant! What is a better way to do this?
LazyVGrid can be made to achieve your two-column layout:
struct ContentView: View {
let theWords = ["Cat","Dog","Rat","Hamster","Iguana","Newt"]
let columns: [GridItem] =
Array(repeating: .init(.flexible()), count: 2)
var body: some View {
LazyVGrid(columns: columns) {
ForEach(theWords, id: \.self) { word in
Text(word)
}
}
}
}
Important note:
Using .self for the id in a ForEach is not safe unless it is guaranteed that each word is unique and won't change locations in the grid (such as in my example where theWords is an immutable array). If either of those is not the case, make sure to give each word a unique ID
I have a ForEach loop which works with index, how I can make this ForEach step get custom step, because in ForEach in every single loop, it will add one to index, how we cam make step be 5, it means in every single loop adds 5 instead 1. I can make it possible with if and %, but I do not want ForEach try every each Item inside Items. how we can make it efficient coding?
Code:
struct ContentView: View
{
var body: some View
{
List
{
ForEach(0 ..< 21) { index in // (Step == 5) not 1
Text("Hello, world!")
.padding()
}
}
}
}
Here is possible approach
List
{
ForEach(Array(stride(from: 0, to: 21, by: 5)), id: \.self) { index in // (Step == 5) not 1
Text("Hello, world!")
.padding()
}
}
I'm new to Swift development, and I'm trying to make a View, where you can click an item and it gets bigger, while the old big item gets smaller. I'm using an #State var called chosen to know which Element should be big at the moment. The items itself are Views with a Button on top. The idea is, that I click the button and the button will change the chosen variable, which is working. But it seems that my view doesn't redraw itself and everything stays as is. The simplified pseudocode looks like this:
struct MyView: View {
#State var chosen = 0
var body: some View {
VStack(){
ForEach(0 ..< 4) { number in
if self.chosen == number {
DifferentView()
.frame(big)
.clipShape(big)
}else{
ZStack{
DifferentView()
.frame(small)
.clipShape(small)
Button(action: {self.chosen = number}){Rectangle()}
}
}
}
}
}
You're using this overload of ForEach.init(_:content:), which accepts a constant range. While your range doesn't change, it also appears to be that this ForEach variant doesn't update the content (it was surprising to me).
You need to use the following overload: ForEach.init(_:id:content:) - supplying id with a keypath:
ForEach(0 ..< 4, id: \.self) { number in
// ...
}
But because there is a conditional, it trips up SwiftUI (hard to know why). The way to avoid it is to wrap it in something, like a Group or a ZStack, or even a function that generates the inner view:
ForEach(0 ..< 4, id: \.self) { number in
Group {
self.chosen == number {
// ...
} else {
// ...
}
}
}
Or, like so:
ForEach(0 ..< 4, id: \.self) { number in
self.inner(for: number)
}
#ViewBuilder
func inner(for number: Int) -> some View {
self.chosen == number {
// ...
} else {
// ...
}
}
I'm trying to create a LazyVGrid view to display the contents of objects in an array using nested ForEach statements. The code is causing an app crash with the message "Fatal error: each layout item may only occur once".
Each object contains an array of values that need to be displayed as a row in the grid. The row consists of cell with the object's sku string followed by a number of cells with the integers from the object's array.
I chose to use a class instead of a struct because I need to update the array within the class.
This is the class for the objects in the array.
class RoomPickupData: Identifiable {
let id = UUID()
var sku: String = ""
// Array of counts for sku on a particular date
var roomsForDate: [Date: Int] = [:]
}
In the code the first ForEach is used to put header information in the first line of the grid.
The next ForEach and the ForEach nested in it are causing the error.
The outer ForEach iterates through the array of objects so I can create a row in the grid for each object. Each row consists of a string followed values from the roomsForDate array. The size of the roomsForDate array is not fixed.
If I comment out the nested ForEach the code executes but with it I get the fatal error.
If I comment out Text(roomData.value.description) so there is noting in the inner ForEach, the code runs fine too. If I replace Text(roomData.value.description) with Text("") the app crashes.
ForEach nesting seems like the best way to accomplish producing a grid view from a two dimensional array or array of objects each containing an array.
I've found other posts showing that nested ForEach statements can be used in SwiftUI.
Here is the code. Your help with this issue is appreciated.
struct RoomTableView: View {
#ObservedObject var checkfrontVM: CheckFrontVM
let dateFormatter = DateFormatter()
init(checkfrontVM: CheckFrontVM) {
self.checkfrontVM = checkfrontVM
dateFormatter.dateFormat = "MM/dd/yyyy"
}
func initColumns() -> [GridItem] {
var columns: [GridItem]
var columnCount = 0
if self.checkfrontVM.roomPickupDataArray.first != nil {
columnCount = self.checkfrontVM.roomPickupDataArray.first!.roomsForDate.count
} else {
return []
}
columns = [GridItem(.flexible(minimum: 100))] + Array(repeating: .init(.flexible(minimum: 100)), count: columnCount)
return columns
}
var body: some View {
if self.checkfrontVM.roomPickupDataArray.first != nil {
ScrollView([.horizontal, .vertical]) {
LazyVGrid(columns: initColumns()) {
Text("Blank")
ForEach(self.checkfrontVM.roomPickupDataArray.first!.roomsForDate.sorted(by: {
$0 < $1
}), id: \.key) { roomData in
Text(dateFormatter.string(from: roomData.key))
}
ForEach(self.checkfrontVM.roomPickupDataArray) { roomPickupData in
Group {
Text(roomPickupData.sku)
.frame(width: 80, alignment: .leading)
.background(roomTypeColor[roomPickupData.sku])
.border(/*#START_MENU_TOKEN#*/Color.black/*#END_MENU_TOKEN#*/)
ForEach(roomPickupData.roomsForDate.sorted(by: { $0.key < $1.key}), id: \.key) { roomData in
Text(roomData.value.description)
}
}
}
}
.frame(height: 600, alignment: .topLeading)
}
.padding(.all, 10)
}
}
}
Every item in ForEach needs a unique ID. The code can be modified by adding the following line to the Text view in the inner ForEach to assign a unique ID:
.id("body\(roomPickupData.id)-\(roomData.key)")
ForEach(self.checkfrontVM.roomPickupDataArray) { roomPickupData in
Group {
Text(roomPickupData.sku)
.frame(width: 80, alignment: .leading)
.background(roomTypeColor[roomPickupData.sku])
.border(Color.black)
ForEach(roomPickupData.roomsForDate.sorted(by: { $0.key < $1.key}), id: \.key) { roomData in
Text(roomData.value.description)
.id("body\(roomPickupData.id)-\(roomData.key)") //<-
}
}
}
I was facing the same error for ForEach inside ForEach inside a LazyVStack I have solved it by adding the UUID() identifier to the innermost View like
.id(UUID)
Here is an example:
LazyVStack {
ForEach(vmHome.allCategories, id: \.self) { category in
VStack {
HStack {
Text(category)
.font(.title3)
.bold()
.padding(10)
Spacer()
}
ScrollView(.horizontal) {
LazyHStack{
ForEach(vmHome.getMovies(forCat: category), id: \.self.id) { movie in
StandardHomeMovieView(movie: movie)
.id(UUID())
}
}
}
}
}
}
It's common practice to have some data that has multiple properties in an array to be displayed in a device of a viewport, that is either compact or wide (iPhone, iPad, portrait/landscape, etc). For example, we might want to show a 1 column of 2 items in "portrait compact view", or a 1 column and 4 items in a "portrait wide view".
Code wise, we have the UserInterfaceSizeClass, that can be used as follows:
#Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass?
#Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass?
...
HStack {
if horizontalSizeClass == .compact {
...
} else {
...
}
}
Or, something like calculating the ratio:
GeometryReader { proxy in
if proxy.size.width > 324.0/2.0 {
WideView()
} else {
NarrowView()
}
}
And a grid can be understood as a 2D array, that we can iterate over the desired number of columns and nested in the loop, loop through the desired number of rows.
VStack {
ForEach(0 ..< self.rows, id: \.self) { row in
HStack {
ForEach(0 ..< self.columns, id: \.self) { column in
...
}
}
}
}
I hope that this far makes sense and comprehended as some basic principles used independently of the tech stack.
So, given a list of 1-dimensional collection of data (please assume a big number of planets):
class Planet {
var name: String
var size: Double
init(name:String, size:Double) {
self.name = name
self.size = size
}
}
var planets = [Planet]()
let planet = Planet(name: "Mars", size: 30.5)
planets.append(planet)
The data needs to be allocated to the response grid view.
So, what's the best approach to create a responsive grid layout in SwiftUI, considering the data and the different viewports and device portrait/landscape modes exposed above?
Let know if there are good practices to approach this!