How can I show different view for each items of a foreach loop in swiftui?
Use an if
statement within your HStack
to display the correct view based on the value of selectedCategory
:
HStack {
if selectedCategory == "A+" {
APositiveView()
} else if selectedCategory == "B+" {
BPositiveView()
} else if ...
ForEach(categories, id: \.self) { category in
// ...
}
}
Though a better way to approach this would be to have a single view that displays such categorical information:
struct CategoryView: View {
@Binding var category: String
var body: some View {
// Display based on the value of category
}
}
HStack {
CategoryView(category: $selectedCategory)
ForEach(categories, id: \.self) { category in
// ...
}
}
sheet inside ForEach doesn't loop over id's
You have sheet
for each item in your list, and all of them are getting isSheetPresented
value. Which one will be displayed is undefined
Instead you need to store selectedItem
and pass it to single sheet
, like this:
struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
@State var selectedItem: Item?
var body: some View{
NavigationView{
ScrollView {
LazyVGrid(columns: gridLayout, spacing: 15, content: {
ForEach(viewModel.items, id: \.id) { item in
VStack(alignment: .leading, spacing: 5){
Button(action: {
selectedItem = item
}, label: {
Image(item.imageUrl)
.resizable()
.scaledToFit()
.padding(10)
})
}//:VSTACK
.scaledToFit()
.padding()
.background(Color.white.cornerRadius(12))
.background(RoundedRectangle(cornerRadius: 12).stroke(Color.gray, lineWidth: 1))
}//: LOOP FOR EACH
}).padding(5)
.onAppear(perform: {
viewModel.loadData()
viewModel.postData()
})
.sheet(item: $selectedItem, content: { selectedItem in
WebView(url: selectedItem.link)
})
}
.navigationBarHidden(true)
}//: NAVIGATION VIEW
} //: BODY
}
SwiftUI avoid ForEach rendering all items when only one changes
Just make model Equatable
so rendering engine could detect if dependent property is really changed.
Tested with Xcode 13.4 / iOS 15.5
struct Player: Equatable { // << here !!
var name: String
var score: Int
}
Nested If statements inside ForEach loop
Here's one solution, using my solution mentioned in the comments, using if let = ...
to do optional binding (read more on that concept here https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html):
struct Home: View {
var datas: [DataArray] = ListDataArray.dot
var body: some View {
ScrollView {
LazyVStack(spacing: 10) {
ForEach (datas, id: \.id) { data in
if let name1 = data.name1 {
HStack {
Image(systemName: "1.circle.fill")
.font(.title2)
.foregroundColor(.red)
Text(name1)
Spacer()
}
}
if let name2 = data.name2 {
HStack {
Image(systemName: "2.circle.fill")
.font(.title2)
.foregroundColor(.red)
Text(name2)
Spacer()
}
}
if let name3 = data.name3 {
HStack {
Image(systemName: "3.circle.fill")
.font(.title2)
.foregroundColor(.red)
Text(name3)
Spacer()
}
}
if let name4 = data.name4 {
HStack {
Image(systemName: "4.circle.fill")
.font(.title2)
.foregroundColor(.red)
Text(name4)
Spacer()
}
}
}
}
}
}
}
Here's a further refactored version that splits the inner view into a separate function so that the code isn't repeated:
struct Home: View {
var datas: [DataArray] = ListDataArray.dot
@ViewBuilder func segment(imageName: String, text: String) -> some View {
HStack {
HStack {
Image(systemName: imageName)
.font(.title2)
.foregroundColor(.red)
Text(text)
Spacer()
}
}
}
var body: some View {
ScrollView {
LazyVStack(spacing: 10) {
ForEach (datas, id: \.id) { data in
if let name1 = data.name1 {
segment(imageName: "1.circle.fill", text: name1)
}
if let name2 = data.name2 {
segment(imageName: "2.circle.fill", text: name2)
}
if let name3 = data.name3 {
segment(imageName: "3.circle.fill", text: name3)
}
if let name4 = data.name4 {
segment(imageName: "4.circle.fill", text: name4)
}
}
}
}
}
}
I've found that when parsing SwiftUI, the compiler has trouble evaluating what would seem like simple boolean expressions. In other words, your original code should actually work, but the compiler has trouble parsing it.
How to reload ForEach loop items after sheet is dismissed
Check out your console, you'll see following warning:
ForEach<Range, Int, ModifiedContent<Text, AddGestureModifier<_EndedGesture>>> count (2) != its initial count (1).
ForEach(_:content:)
should only be used for constant data. Instead conform data toIdentifiable
or useForEach(_:id:content:)
and provide an explicitid
!
You could replace 0..<viewModel.players.count
with viewModel.players.indices
to fix it
But if you do, you'll face a crash, because you're initializing selectedItem
in the init, and when new item gets added it doesn't have enough items
You can add new false
to this array somehow, but if you'd like to add new item in the begging, you'll have more problems with up-to-dating your selectedItem
flags array
Instead of that I suggest you adding a unique identifier to your Player
item and storing Set
of this ids for selected player
struct PlayersView: View {
@ObservedObject var viewModel: PlayersViewModel
@State var isSelected: Bool = false
@State private var selectedIds = Set<String>()
@State var selectedPlayers = [String]()
init(viewModel: PlayersViewModel) {
self.viewModel = viewModel
}
var body: some View {
VStack {
ForEach(viewModel.players) { player in
let selected = selectedIds.contains(player.id)
Text("\(String(describing: selected)) \(player.playerName)")
.onTapGesture {
if selected {
selectedIds.remove(player.id)
} else {
selectedIds.insert(player.id)
}
selectedPlayers = viewModel.players
.filter { selectedIds.contains($0.id) }
.map { $0.playerName }
}
}
}
}
}
struct Player: Codable, Identifiable, Hashable {
let id: String
let playerName: String
}
class PlayersViewModel: ObservableObject {
@Published var players = [Player]()
init() {
readJSON()
}
func saveJSON(username: String) {
var array = players
array.append(Player(id: UUID().uuidString, playerName: username))
do {
let fileURL = try FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("example2.json")
try JSONEncoder().encode(array)
.write(to: fileURL)
readJSON()
} catch {
print(error.localizedDescription)
}
}
func readJSON() {
do {
let fileURL = try FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
.appendingPathComponent("example2.json")
let data = try Data(contentsOf: fileURL)
let players = try JSONDecoder().decode([Player].self, from: data)
self.players = players
print(players)
} catch {
print(error.localizedDescription)
}
}
}
Can a Swiftui Identifiable Foreach loop select only the first 5 items
you could try something like this:
if (introductions.count >= 5) {
ForEach(introductions.prefix(upTo: 5), id:\.id) { introduction in
NavigationLink(destination: IntroductionDetailView(introduction: introduction)) {
IntroductionsView(introduction: introduction)
}
}
}
Issues with ForEach Loop over view model array causing Xcode compile error - SwiftUI
Based on the comments above by loreipsum and George, my issue was that GroupCellViewModel didn't conform to Identifiable. I fixed this and that solved the issue:
class GroupCellViewModel: ObservableObject, Identifiable {
@Published var groupRepository: GroupStoreType
@Published var group: AccountabilityGroup
private var cancellables = Set<AnyCancellable>()
init(groupRepository: GroupStoreType, currentUser: CurrentUserType = CurrentUserProfile.shared, accountabilityGroup: AccountabilityGroup) {
self.groupRepository = groupRepository
self.group = accountabilityGroup
}
}
How to use if/ForEach in a SwiftUI View to show IAP modal?
Instead of using .onAppear
modifier to display the modal, you can change the initial values of selection
and showModal
:
@State private var selection: String? = "Pro"
@State private var showModal = !UserDefaults.standard.bool(forKey: "xxxxxxxxxxxxxxxxxxxxx") ? true : false
// Write your product identifier instead of "xxxxxxxxxxxxxxxxxxxxx"
This way, modal view will be shown instantly after the content view loads.
Note: For showModal
, I've applied a conditional if
instead of simply true
, since you said you want to show the modal only to those who have not subscribed yet to the Pro IAP.
Related Topics
How to Stop a Dispatchworkitem in Gcd
Differences in Nsdatecomponents Syntax
How to Display an Activity Indicator With Text on iOS 8 With Swift
Saving Picked Image to Coredata
Swift Lazy Instantiating Using Self
Why My Return Is Nil But If I Press the Url in Chrome/Safari, I Can Get Data
Printing a Variable Memory Address in Swift
How to Get Current Language Code With Swift
What Does "@Uiapplicationmain" Mean
How to Detect If an Skspritenode Has Been Touched
How to Create a Segue That Can Be Called from a Button That Is Created Programmatically
Programmatically Detect Tab Bar or Tabview Height in Swiftui
Forced to Cast, Even If Protocol Requires Given Type
Why Are Emoji Characters Like 👩👩👧👦 Treated So Strangely in Swift Strings
How to Set Layer Cornerradius For Only Bottom-Left, Bottom-Right, and Top-Left Corner