I'm trying to create a grid view in swiftui and it's not working out for me. Can someone tell me what I'm doing wrong? I tried using a c style for loop where I can set a variable and increment it, but swiftui throws an error when I do that.
struct ProductSubCat: View {
#State var productCategory = String()
#State var number = Int()
#ObservedObject var allData = WebserviceGetDataSolutions()
#State var theCount = 0
var body: some View {
ScrollView {
Spacer()
ForEach(0 ..< self.allData.posts[self.number].productLines.count / 2, id: \.self) {row in
HStack {
ForEach(0..<2) {cell in
NavigationLink(destination: ProductDetails())
{
Text(self.allData.posts[self.number].productLines[row].system)
Image("stonclad")
.resizable()
.aspectRatio(contentMode: .fit)
}.buttonStyle(PlainButtonStyle())
}
}
}
}
}
}
Use an Array extension to split the list into smaller groups of size:
extension Array {
func chunks(_ size: Int) -> [[Element]] {
stride(from: 0, to: self.count, by: size).map { ($0 ..< Swift.min($0 + size, self.count)).map { self[$0] } }
}
}
This can then be used to create something like a simple grid View, for example:
struct Product: Identifiable, Hashable {
var id = UUID()
var name: String
}
struct SimpleGridViewExample: View {
var products = [Product(name: "p1"), Product(name: "p2"), Product(name: "p3"), Product(name: "p4"), Product(name: "p5")]
var body: some View {
ScrollView {
VStack(alignment: .leading) {
ForEach(products.chunks(2), id: \.self) { chunk in
HStack {
ForEach(chunk, id: \.self) { product in
VStack {
Image(systemName: "sun.max")
.resizable()
.frame(maxWidth: 100, maxHeight: 100)
.aspectRatio(contentMode: .fit)
Text(product.name)
}
}
}
}
}
}
}
}
Related
Model
I have different variables in Travel.
import Foundation
struct Travel: Identifiable, Codable {
var id = UUID()
var name: String
var date = Date()
var location: String
var isFav: Bool
var score: Float
var comment: String
}
View model
I load and save data with UserDefaults. Always its work but in this model not.
import Foundation
class TravelViewModel: ObservableObject {
#Published var travelList = [Travel] ()
#Published var travelled = 0
init(){
load()
}
func load() {
guard let data = UserDefaults.standard.data(forKey: "travelList"),
let savedTravels = try? JSONDecoder().decode([Travel].self, from: data) else { travelList = []; return }
travelList = savedTravels
}
func save() {
do {
let data = try JSONEncoder().encode(travelList)
UserDefaults.standard.set(data, forKey: "travelList")
} catch {
print(error)
}
}
}
Adding Item View
I have addItems func and use this func in addItem button.
import SwiftUI
struct AddTravelView: View {
#StateObject var VM = TravelViewModel()
#State var name = ""
#State var location = ""
#State var isFav = false
#State var score = 0.00
#State var comment = ""
var body: some View {
VStack {
ZStack {
Rectangle()
.fill(.black.opacity(0.2))
.cornerRadius(20)
.frame(width: 350, height: 350)
VStack{
HStack {
Text("Name:")
.font(.system(size: 16 , weight: .medium))
TextField("Type..", text: $name)
}
HStack {
Text("Location:")
.font(.system(size: 16 , weight: .medium))
TextField("Type..", text: $location)
}
HStack {
Text("Score: \(Int(score))")
.font(.system(size: 16 , weight: .medium))
Slider(value: $score, in: 0...10, step: 1)
}
Spacer()
ZStack(alignment: .topLeading) {
Rectangle()
.fill(.white)
.cornerRadius(20)
.frame(height: 200)
VStack(alignment: .leading) {
TextField("Comment...", text: $comment, axis: .vertical)
}.padding()
}
}
.padding()
.frame(width: 300, height: 200)
}
Button {
addTravel()
} label: {
ZStack{
Rectangle()
.fill(.black.opacity(0.2))
.cornerRadius(20)
.frame(width: 350 , height: 100)
Text("ADD TRAVEL")
.font(.system(size: 25, weight: .medium, design: .monospaced))
.foregroundColor(.black)
}.padding()
}
}
}
func addTravel(){
VM.travelList.append(Travel(name: name, location: location, isFav: isFav, score: Float(score), comment: comment))
}
}
struct AddTravelView_Previews: PreviewProvider {
static var previews: some View {
AddTravelView()
}
}
Recent Adds view
In this page i wanna see Items i add before
import SwiftUI
struct RecentTravels: View {
#StateObject var VM = TravelViewModel()
var body: some View {
VStack {
ForEach(VM.travelList) {Travel in
HStack{
Image(systemName: "questionmark")
.frame(width: 50, height: 50)
.padding()
.overlay {
RoundedRectangle(cornerRadius: 5)
.stroke(.black, lineWidth: 2)
}
VStack(alignment: .leading) {
Text(Travel.name)
.font(.subheadline)
.bold()
.lineLimit(1)
Text("\(Travel.date)")
.font(.footnote)
.opacity(0.9)
.lineLimit(1)
}
Spacer()
VStack {
Image(systemName: "heart")
Spacer()
Text("\(Travel.score)")
}
.frame(height: 50)
.font(.system(size: 22))
}
}
}
}
}
struct RecentTravels_Previews: PreviewProvider {
static var previews: some View {
RecentTravels()
}
}
And ContentView
and calling those 2 views in ContentView.
import SwiftUI
struct ContentView: View {
#StateObject var VM = TravelViewModel()
var body: some View {
VStack {
AddTravelView()
RecentTravels()
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
When i write the all code in only ContentView is work but when i call another pages its not work. Usually it was work when i presss add item button and restart app but now its nothing. Its not work even restart the app.
The problem you have is that you have multiple VM, that have no relations to each other. You must not have more than one source of truth
in #StateObject var VM = TravelViewModel().
Keep the one you have in ContentView,
and pass it to the other view like this:
VStack {....}.environmentObject(VM).
In your AddTravelView and RecentTravels ,
add #EnvironmentObject var VM: TravelViewModel instead of #StateObject var VM = TravelViewModel().
Have a look at this link, it gives you some good examples of how to manage data in your app:
https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app
I am working on minimizable side menu with collapsable sub menus in swift. Currently both the minimizing and collapsable submenu components are working. I have spent all evening trying to set the background color of the menu items. As it stands listRowBackground does nothing. I have come across other stack overflow (and other websites) posts talking about this. Some say to use for each loops. Below I have another SideMenu struct using a for each loop. This loop moves the side menu to the center of the screen. Can someone help me with either of these approaches to keep the menu on the left hand side of the screen and change the row background color?
Any help is appreciated!
Edit: I have also updated Xcode to version 14, but it did not help.
import SwiftUI
struct SideMenu: View {
let width: CGFloat
#Binding var menuOpened: Bool
var items: [Menu] = [.item0, .a, .b, .c]
var body: some View {
ZStack {
VStack(alignment: .leading){
HStack{
List(items, children: \.items){ row in
HStack{
if row.imageName != ""{
let image = Image(systemName: row.imageName)
image.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.black)
.frame(width: 32, height: 32, alignment: .center)
}else{
Text(row.name).listRowBackground(Color.orange)
}
}.listRowBackground(Color.orange)
.onTapGesture {
if row.imageName == "line.horizontal.3"{
menuOpened.toggle()
}
}
}
.background(Color.red)
.listStyle(.plain)
.frame(width: menuOpened ? 275 : 80)
Spacer()
}
}
}
}
}
struct Menu: Identifiable{
let id = UUID()
var name = ""
var imageName = ""
var items: [Menu]?
}
struct ContentView: View {
#State var menuOpened = true
var body: some View {
SideMenu(width: UIScreen.main.bounds.width/1.6, menuOpened: $menuOpened)
}
}
extension Menu{
static var item1 = Menu(name: "a1")
static var item2 = Menu(name: "a2")
static var item3 = Menu(name: "a3")
static var item0 = Menu(imageName: "line.horizontal.3")
static var a = Menu(name: "a", items: [.item1, .item2, item3])
static var b = Menu(name: "b")
static var c = Menu(name: "Settings")
}
Here is a struct with a for each loop
struct SideMenu: View {
let width: CGFloat
#Binding var menuOpened: Bool
var items: [Menu] = [.item0, .a, .b, .c]
var body: some View {
ZStack {
VStack(alignment: .leading){
HStack{
List {
ForEach(items) { row in
HStack{
if row.imageName != ""{
let image = Image(systemName: row.imageName)
image.resizable()
.aspectRatio(contentMode: .fit)
.foregroundColor(.black)
.frame(width: 32, height: 32, alignment: .center)
}else{
Text(row.name).listRowBackground(Color.orange)
}
}.listRowBackground(Color.orange)
.onTapGesture {
if row.imageName == "line.horizontal.3"{
menuOpened.toggle()
}
}
}
.background(Color.red)
.listStyle(.plain)
.frame(width: menuOpened ? 275 : 80)
Spacer()
}
}
}
}
}
}
I have a view as show in the below image
Segments will be dynamic, so i have followed loop mechanism
HStack {
for() {
VStack {
HStack {
Image
Line View/. Don't show for last item
}
Text
}
}
}
I have tried with alignmentGuide and resulting the below image
Getting Text, Circles and lines aligned is trickier than I expected. I only made it with some hard-coded values, but at least it is flexible to some extent.
struct Step: Identifiable {
var id: Int
var name: String
var completed: Bool = false
}
struct OrderStateView: View {
var steps: [Step]
var body: some View {
ZStack {
// circles
HStack(spacing: 0) {
Spacer()
ForEach(steps.indices) { index in
let completed = steps[index].completed
Image(systemName: completed ?
"checkmark.circle.fill" : "circle")
.font(.title2)
.foregroundColor(completed ? .green : .gray)
.overlay {
Text(steps[index].name)
.font(.caption)
.multilineTextAlignment(.center)
.offset(x: 0, y: 35)
.frame(minWidth: 70, minHeight: 50)
}
if index < steps.count-1 {
if steps[index+1].completed {
Color.green
.frame(height: 1)
} else {
Color.gray
.frame(height: 1)
}
}
}
}
Spacer()
}
.padding(.bottom, 35)
}
}
// example usage
struct ContentView: View {
#State private var steps = [
Step(id: 0, name: "Customer Details"),
Step(id: 1, name: "Order Type"),
Step(id: 2, name: "Add Products"),
Step(id: 3, name: "Order")
]
#State private var step = 0
var body: some View {
VStack {
Text("Your Order State")
.padding()
OrderStateView(steps: steps)
Button("Continue") {
steps[step].completed = true
if step < steps.count-1 {
step += 1
}
}
.buttonStyle(.bordered)
.padding()
}
.padding()
}
}
Hi I'm learning SwiftUI and I encountered a problem while trying to build an app. My question is how can I make a tab bar that changes a systemImage while active (when the home button is active it shows "home.fill", and when the user presses the search button, the home button changes to "home").
If you see any improvement that I could make in the code below it's appreciated as well. Thank you, have a nice day, and stay safe.
import SwiftUI
struct ContentView: View {
#State private var fullScreenCover = false
init() {
UITabBar.appearance().isTranslucent = false
}
var body: some View {
VStack {
ForEach(0..<6) { num in
Spacer()
}
HStack {
Spacer()
Button(action: {}, label: {
Image(systemName: "message")
.font(.system(size: 20))
})
.padding(.horizontal, 15)
}
.foregroundColor(Color("Reverse"))
.padding(.vertical, 8)
TabView {
Main_Home()
.tabItem {
Image(systemName: "house")
.font(.system(size: 30))
}
Main_Search()
.tabItem {
Image(systemName: "magnifyingglass")
.font(.system(size: 30))
}
Main_AddContent()
.tabItem {
Image(systemName: "plus.viewfinder")
.font(.system(size: 30))
}
Main_Message()
.tabItem {
Image(systemName: "pencil.circle.fill")
.font(.system(size: 30))
}
Main_Profile()
.tabItem {
Image(systemName: "person.crop.circle")
.font(.system(size: 30))
}
}
.accentColor(Color("Reverse"))
}
.background(Color("Normal"))
.edgesIgnoringSafeArea(.all)
}
}
Here is a custom ViewModifier that you can use for adding tab items:
public struct TabViewItem<SelectionValue>: ViewModifier where SelectionValue: Hashable {
#Binding private var selectedIndex: SelectionValue
private let index: SelectionValue
private let text: String
private let imageName: String
public init(selectedIndex: Binding<SelectionValue>, index: SelectionValue, text: String, imageName: String) {
self._selectedIndex = selectedIndex
self.index = index
self.text = text
self.imageName = imageName
}
public func body(content: Content) -> some View {
content
.tabItem {
image
Text(text)
}
.tag(index)
}
private var image: some View {
guard selectedIndex == index else {
return Image(systemName: imageName)
}
let imageName = self.imageName + ".fill"
if let uiImage = UIImage(systemName: imageName) {
return Image(uiImage: uiImage)
}
return Image(systemName: self.imageName)
}
}
public extension View {
func tabItem<Selection>(
_ text: String,
imageName: String,
index: Selection,
selection: Binding<Selection>
) -> some View where Selection: Hashable {
modifier(TabViewItem(selectedIndex: selection, index: index, text: text, imageName: imageName))
}
}
Then, creating the TabView is more straightforward:
struct ContentView: View {
#State private var selectedTab = 3
var body: some View {
TabView(selection: $selectedTab) {
Text("Main_Home")
.tabItem("Home", imageName: "house", index: 1, selection: $selectedTab)
Text("Main_Search")
.tabItem("Search", imageName: "magnifyingglass", index: 2, selection: $selectedTab)
Text("Main_AddContent")
.tabItem("AddContent", imageName: "plus.viewfinder", index: 3, selection: $selectedTab)
Text("Main_Message")
.tabItem("Message", imageName: "pencil.circle", index: 4, selection: $selectedTab)
Text("Main_Profile")
.tabItem("Profile", imageName: "person.crop.circle", index: 5, selection: $selectedTab)
}
}
}
Note: The above implementation is for generic purposes - if you don't need some parts (like text in tabs) just remove it or make them optional.
I'm creating a simple form app. In that, I have checkboxes and Radio buttons, but I don't know how to do it.
I have done below code to dynamically change the selected option's colour. But it can be select multiple values. I need to select only one value among 5 values like a radio button.
E.g:
I'm taping on the second radio button. Now if I select the fourth radio button, the second one should deselect and the fourth one should get selected.
struct DCTableCell: View {
#Binding var dcValue: String
#State var isSelected: Bool = false
var body: some View {
Button(action: {
print("Tapped")
self.isSelected.toggle()
}){
ZStack {
RoundedRectangle(cornerRadius: 8)
.stroke(self.isSelected ? Color.init("borderSelected"): Color.init("border"))
.frame(height: 56)
.foregroundColor(.clear)
HStack {
Text(dcValue)
.font(.custom("Montserrat", size: 16))
.fontWeight(.medium)
.foregroundColor(self.isSelected ? Color.init("borderSelected") : .white)
.padding()
Spacer()
ZStack {
Circle()
.stroke(self.isSelected ? Color.init("borderSelected") : Color("circleBorder"))
.frame(width: 18, height: 18)
.padding()
Circle()
.frame(width: 10, height: 10)
.foregroundColor(self.isSelected ? Color.init("borderSelected"): Color.clear)
}
}
}
}
}
}
check this out...an easy to use SwiftUI RadiobuttonGroup for iOS
you can use it like this:
RadioButtonGroup(items: ["Rome", "London", "Paris", "Berlin", "New York"], selectedId: "London") { selected in
print("Selected is: \(selected)")
}
and here is the code:
struct ColorInvert: ViewModifier {
#Environment(\.colorScheme) var colorScheme
func body(content: Content) -> some View {
Group {
if colorScheme == .dark {
content.colorInvert()
} else {
content
}
}
}
}
struct RadioButton: View {
#Environment(\.colorScheme) var colorScheme
let id: String
let callback: (String)->()
let selectedID : String
let size: CGFloat
let color: Color
let textSize: CGFloat
init(
_ id: String,
callback: #escaping (String)->(),
selectedID: String,
size: CGFloat = 20,
color: Color = Color.primary,
textSize: CGFloat = 14
) {
self.id = id
self.size = size
self.color = color
self.textSize = textSize
self.selectedID = selectedID
self.callback = callback
}
var body: some View {
Button(action:{
self.callback(self.id)
}) {
HStack(alignment: .center, spacing: 10) {
Image(systemName: self.selectedID == self.id ? "largecircle.fill.circle" : "circle")
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: self.size, height: self.size)
.modifier(ColorInvert())
Text(id)
.font(Font.system(size: textSize))
Spacer()
}.foregroundColor(self.color)
}
.foregroundColor(self.color)
}
}
struct RadioButtonGroup: View {
let items : [String]
#State var selectedId: String = ""
let callback: (String) -> ()
var body: some View {
VStack {
ForEach(0..<items.count) { index in
RadioButton(self.items[index], callback: self.radioGroupCallback, selectedID: self.selectedId)
}
}
}
func radioGroupCallback(id: String) {
selectedId = id
callback(id)
}
}
struct ContentView: View {
var body: some View {
HStack {
Text("Example")
.font(Font.headline)
.padding()
RadioButtonGroup(items: ["Rome", "London", "Paris", "Berlin", "New York"], selectedId: "London") { selected in
print("Selected is: \(selected)")
}
}.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct ContentViewDark_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environment(\.colorScheme, .dark)
.darkModeFix()
}
}
Ok this is not an ideal solution but it works and hopefully opens your eyes to improve what you have. I give every RadioButton an ID and when the selected ID changes it updates:
struct DCTableCell: View {
var id: Int
#Binding var dcValue: String
#Binding var selectedID: Int
var body: some View {
Button(action: {
print("Tapped")
self.selectedID = self.id
}){
ZStack {
RoundedRectangle(cornerRadius: 8)
.stroke(self.id == self.selectedID ? Color.blue : Color.white)
.frame(height: 56)
.foregroundColor(.clear)
HStack {
Text(dcValue)
.font(.custom("Montserrat", size: 16))
.fontWeight(.medium)
.foregroundColor(self.id == self.selectedID ? .blue : .white)
.padding()
Spacer()
ZStack {
Circle()
.stroke(self.id == self.selectedID ? Color.blue : .black)
.frame(width: 18, height: 18)
.padding()
Circle()
.frame(width: 10, height: 10)
.foregroundColor(self.id == self.selectedID ? Color.blue: Color.clear)
}
}
}
}
}
}
And here how to use it. maybe you should create an array with ids and the strings that you want to pass in.
struct ContentView: View {
#State var str = "lolz"
#State var selectedID = -1
var body: some View {
VStack {
ForEach((1...5), id: \.self) { index in
DCTableCell(id: index, dcValue: self.$str, selectedID: self.$selectedID)
}
}
}
}
I hope this helps!
I have a similar solution, use the labels as the hashable tag which makes it very straight. So you only need to setting the outer layer: CustomDCPicker, like a general picker.
UIHostingController(rootView: CustomDCPicker())
struct CustomDCPicker: View {
#State var dcValue: String = ""
var body: some View {
VStack{
Text(dcValue).bold()
DCTable.init(dcValue: $dcValue, Labels: ["sample1","sample2","sample3","sample4","sample5"])
}
}
}
struct DCTable: View {
#Binding var dcValue: String
var Labels: [String] = []
var body: some View {
ForEach(Labels, id:\.self){
DCTableCell(dcValue: self.$dcValue, myLabel: $0)
}
}
}
struct DCTableCell: View {
#Binding var dcValue: String
var isSelected: Bool {
get{ self.dcValue == self.myLabel}
}
var myLabel : String
var body: some View {
Button(action: {
print("Tapped")
self.dcValue = self.myLabel
}){
ZStack {
RoundedRectangle(cornerRadius: 8.0)
.stroke(self.isSelected ? Color.red: Color.yellow)
.frame(height: 56)
.foregroundColor(.clear)
HStack {
Text(myLabel)
//.font(.custom("Montserrat", size: 16))
.fontWeight(.medium)
.foregroundColor(self.isSelected ? Color.red : .black)
.padding()
Spacer()
ZStack {
Circle()
.stroke(self.isSelected ? Color.red : Color.black)
.frame(width: 18, height: 18)
.padding()
Circle()
.frame(width: 10, height: 10)
.foregroundColor(self.isSelected ? Color.red: Color.clear)
}
}
}
}
}
}