I want to create a list of ready-made cards. I wrote the card in another file, the code below.
struct PopulationPlace: View {
let image: String
let score: String
let title: String
#State private var isFavorite: Bool = false
var body: some View {
Image(image)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 200, height: 210)
.clipShape(RoundedRectangle(cornerRadius: 25.0))
.overlay(alignment: .topTrailing, content: {
Button {
withAnimation{
isFavorite = false
}
} label: {
Image(systemName: "heart.circle.fill")
.symbolRenderingMode(.palette)
.foregroundStyle(.red, .yellow)
.foregroundColor(.red)
.padding(.vertical, 8)
.padding(.leading, 8)
}
.padding()
})
}
}
When I launch it in the preview, the buttons respond to clicks, but when I create a list of cards, the buttons cease to be active. What am I doing wrong?
It doesn't work for me
ScrollView(.horizontal, showsIndicators: false){
HStack{
ForEach(sampleList, id: \SamplePopulation.self){
item in
PopulationPlace(image: item.image, score: item.score, title:item.title)
}
}
}
Sample data
struct SamplePopulation: Identifiable, Hashable{
let id: String = UUID().uuidString
let image: String
let score: String
let title: String
}
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)
}
}
}
}
}
}
}
}
am trying to change the color of the button according to the isSelected state but not working
struct Box: Identifiable {
var id: Int
var title: String
#State var isSelected: Bool
}
struct BoxView: View {
var box: Box
var body: some View{
Button(action: {
self.box.isSelected.toggle()
}){
Text(box.title)
.foregroundColor(.white)
}
.frame(width: 130, height: 50)
.background(self.box.isSelected ? Color.red : Color.blue)
.cornerRadius(25)
.shadow(radius: 10)
.padding(10)
}
}
Try this way.
struct Box: Identifiable {
var id: Int
var title: String
}
struct BoxView: View {
var box: Box
#State var selectedBtn: Int = 1
var body: some View {
ForEach((1...10).reversed(), id: \.self) { item in
Button(action: {
self.selectedBtn = item
}){
Text(self.box.title)
.foregroundColor(.white)
}
.frame(width: 130, height: 50)
.background(self.selectedBtn == item ? Color.red : Color.blue)
.cornerRadius(25)
.shadow(radius: 10)
.padding(10)
}
}
}
you can also observe when value change like this way.
class Box: ObservableObject {
let objectWillChange = ObservableObjectPublisher()
var isSelected = false { willSet { objectWillChange.send() } }
}
struct ContentView: View {
#ObservedObject var box = Box()
var body: some View {
VStack{
Button(action: {
self.box.isSelected.toggle()
}){
Text("tap")
.foregroundColor(.white)
}
.background(box.isSelected ?? false ? Color.red : Color.blue)
.cornerRadius(25)
.shadow(radius: 10)
.padding(10)
}
}
}
You can change Button background Color on click using below code
struct ContentView: View {
#State var isSelected : Bool = false
var body: some View {
VStack {
Button(action: {
self.isSelected.toggle()
}){
Text("State")
.foregroundColor(.white)
}
.frame(width: 130, height: 50)
.background(self.isSelected ? Color.red : Color.blue)
}
}
}
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)
}
}
}
}
}
}
I have a requirement of Checkbox (✅ as in to-do list) with textfield. Currently I have created button object like below :
Button(action: {
// do when checked / unchecked
//...
}) {
HStack(alignment: .top, spacing: 10) {
Rectangle()
.fill(Color.white)
.frame(width:20, height:20, alignment: .center)
.cornerRadius(5)
Text("Todo item 1")
}
}
I need to preserve checked and unchecked state in SwiftUI.
Here is a simple, re-usable checkmark component I created that follows a color scheme similar to other checkmarks on iOS (e.g. selecting messages in the Messages app):
import SwiftUI
struct CheckBoxView: View {
#Binding var checked: Bool
var body: some View {
Image(systemName: checked ? "checkmark.square.fill" : "square")
.foregroundColor(checked ? Color(UIColor.systemBlue) : Color.secondary)
.onTapGesture {
self.checked.toggle()
}
}
}
struct CheckBoxView_Previews: PreviewProvider {
struct CheckBoxViewHolder: View {
#State var checked = false
var body: some View {
CheckBoxView(checked: $checked)
}
}
static var previews: some View {
CheckBoxViewHolder()
}
}
You can use it in other views like this:
...
#State private var checked = true
...
HStack {
CheckBoxView(checked: $checked)
Spacer()
Text("Element that requires checkmark!")
}
...
The best way for iOS devices is to create a CheckboxStyle struct and conform to the ToggleStyle protocol. That allows you to then use the built-in Toggle component provided by Apple.
// CheckboxStyle.swift
import SwiftUI
struct CheckboxStyle: ToggleStyle {
func makeBody(configuration: Self.Configuration) -> some View {
return HStack {
Image(systemName: configuration.isOn ? "checkmark.square" : "square")
.resizable()
.frame(width: 24, height: 24)
.foregroundColor(configuration.isOn ? .blue : .gray)
.font(.system(size: 20, weight: .regular, design: .default))
configuration.label
}
.onTapGesture { configuration.isOn.toggle() }
}
}
// Example usage in a SwiftUI view
Toggle(isOn: $checked) {
Text("The label")
}
.toggleStyle(CheckboxStyle())
On macOS, Apple already has created a CheckboxToggleStyle() that you can use for macOS 10.15+. But it isn't available for iOS - yet.
Toggle seems to work for both macOS and iOS, using the native control on each.
https://developer.apple.com/documentation/swiftui/toggle
A control that toggles between on and off states.
#State var isOn: Bool = true
var body: some View {
Toggle("My Checkbox Title", isOn: $isOn)
.padding()
}
macOS:
iOS:
We can take help of the #State from Apple, which persists value of a given type, through which a view reads and monitors the value.
Working example :
struct CheckboxFieldView: View {
#State var checkState: Bool = false
var body: some View {
Button(action:
{
//1. Save state
self.checkState = !self.checkState
print("State : \(self.checkState)")
}) {
HStack(alignment: .top, spacing: 10) {
//2. Will update according to state
Rectangle()
.fill(self.checkState ? Color.green : Color.red)
.frame(width:20, height:20, alignment: .center)
.cornerRadius(5)
Text("Todo item ")
}
}
.foregroundColor(Color.white)
}
}
Now, you can add CheckboxFieldView()
You'll want something like this:
struct TodoCell: View {
var todoCellViewModel: TodoCellViewModel
var updateTodo: ((_ id: Int) -> Void)
var body: some View {
HStack {
Image(systemName: (self.todoCellViewModel.isCompleted() ? "checkmark.square" : "square")).tapAction {
self.updateTodo(self.todoCellViewModel.getId())
}
Text(self.todoCellViewModel.getTitle())
}
.padding()
}
}
Your list could look something like this:
struct TodoList: View {
var todos: Todos
var updateTodo: ((_ id: Int) -> Void)
var body: some View {
List(self.todos) { todo in
TodoCell(todoCellViewModel: TodoCellViewModel(todo: todo), updateTodo: { (id) in
self.updateTodo(id)
})
}
}
}
Your model might look something like this:
public class TodoCellViewModel {
private var todo: Todo
public init(todo: Todo) {
self.todo = todo
}
public func isCompleted() -> Bool {
return self.todo.completed
}
public func getTitle() -> String {
return self.todo.title
}
public func getId() -> Int {
return self.todo.id
}
}
And finally a Todo class:
public class Todo: Codable, Identifiable {
public let id: Int
public var title: String
public var completed: Bool
}
None of this has actually been tested and not all of the code has been implemented but this should get you on the right track.
Here’s my take on it. I’m actually doing this for MacOS, but the process should be the same.
First, I had to fake the checkbox by creating two png images: and , calling them checkbox-on.png and checkbox-off.png respectively. These I put into Assets.xcassets.
I believe that for iOS, the images are already available.
Second, the view includes a state variable:
#State var checked = false
The rest is to implement a Button with an action, an image, some text, and some modifiers:
Button(action: {
checked.toggle()
}) {
Image(checked ? "checkbox-on" : "checkbox-off")
.renderingMode(.original)
.resizable()
.padding(0)
.frame(width: 14.0, height: 14.0)
.background(Color(NSColor.controlBackgroundColor))
Text("Choose me … !").padding(0)
}
.buttonStyle(PlainButtonStyle())
.background(Color(red: 0, green: 0, blue: 0, opacity: 0.02))
.cornerRadius(0)
checked is the boolean variable you want to toggle
The image depends on the value of the boolean, using the condition operator to choose between the two
renderingMode() ensures that the image appears correctly and resizable() is used to enable frame().
The rest of the modifiers are there to tweak the appearance.
Obviously, if you are going to make a habit of this, you can create a struct:
struct Checkbox: View {
#Binding var toggle: Bool
var text: String
var body: some View {
Button(action: {
self.toggle.toggle()
}) {
Image(self.toggle ? "checkbox-on" : "checkbox-off")
.renderingMode(.original)
.resizable()
.padding(0)
.frame(width: 14.0, height: 14.0)
.background(Color(NSColor.controlBackgroundColor))
Text(text).padding(0)
}
.buttonStyle(PlainButtonStyle())
.background(Color(red: 0, green: 0, blue: 0, opacity: 0.02))
.cornerRadius(0)
}
}
and then use:
Checkbox(toggle: self.$checked, text: "Choose me … !")
(Note that you need to use self.$checked on this one).
Finally, if you prefer to use a common alternative appearance, that of a filled in square for the check box, you can replace Image with:
Rectangle()
.fill(self.autoSave ? Color(NSColor.controlAccentColor) : Color(NSColor.controlColor))
.padding(4)
.border(Color(NSColor.controlAccentColor), width: 2)
.frame(width: 14, height: 14)
I learned a lot doing this, and hopefully, this will help.
Here is my way:
import SwiftUI
extension ToggleStyle where Self == CheckBoxToggleStyle {
static var checkbox: CheckBoxToggleStyle {
return CheckBoxToggleStyle()
}
}
// Custom Toggle Style
struct CheckBoxToggleStyle: ToggleStyle {
func makeBody(configuration: Configuration) -> some View {
Button {
configuration.isOn.toggle()
} label: {
Label {
configuration.label
} icon: {
Image(systemName: configuration.isOn ? "checkmark.square.fill" : "square")
.foregroundColor(configuration.isOn ? .accentColor : .secondary)
.accessibility(label: Text(configuration.isOn ? "Checked" : "Unchecked"))
.imageScale(.large)
}
}
.buttonStyle(PlainButtonStyle())
}
}
struct ContentView: View {
#State var isOn = false
var body: some View {
Toggle("Checkmark", isOn: $isOn).toggleStyle(.checkbox)
}
}
Unchecked:
Checked:
I found this solution here to be much better than using a completely custom made View:
https://swiftwithmajid.com/2020/03/04/customizing-toggle-in-swiftui/
He uses the ToggleStyle protocol to simply change the look of the toggle, instead of rebuilding it:
struct CheckboxToggleStyle: ToggleStyle {
func makeBody(configuration: Configuration) -> some View {
return HStack {
configuration.label
Spacer()
Image(systemName: configuration.isOn ? "checkmark.square" : "square")
.resizable()
.frame(width: 22, height: 22)
.onTapGesture { configuration.isOn.toggle() }
}
}
}
You can use the following code and change the color etc. This is an individual component and I used a callback method to get informed when the checkbox is selected or not.
Step 1: Create a customizable and reusable checkbox view
Step 2: Let use the component in the main view
Use the checkboxSelected() callback function to know which checkbox is selected or not.
import SwiftUI
//MARK:- Checkbox Field
struct CheckboxField: View {
let id: String
let label: String
let size: CGFloat
let color: Color
let textSize: Int
let callback: (String, Bool)->()
init(
id: String,
label:String,
size: CGFloat = 10,
color: Color = Color.black,
textSize: Int = 14,
callback: #escaping (String, Bool)->()
) {
self.id = id
self.label = label
self.size = size
self.color = color
self.textSize = textSize
self.callback = callback
}
#State var isMarked:Bool = false
var body: some View {
Button(action:{
self.isMarked.toggle()
self.callback(self.id, self.isMarked)
}) {
HStack(alignment: .center, spacing: 10) {
Image(systemName: self.isMarked ? "checkmark.square" : "square")
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: self.size, height: self.size)
Text(label)
.font(Font.system(size: size))
Spacer()
}.foregroundColor(self.color)
}
.foregroundColor(Color.white)
}
}
enum Gender: String {
case male
case female
}
struct ContentView: View {
var body: some View {
HStack{
Text("Gender")
.font(Font.headline)
VStack {
CheckboxField(
id: Gender.male.rawValue,
label: Gender.male.rawValue,
size: 14,
textSize: 14,
callback: checkboxSelected
)
CheckboxField(
id: Gender.female.rawValue,
label: Gender.female.rawValue,
size: 14,
textSize: 14,
callback: checkboxSelected
)
}
}
.padding()
}
func checkboxSelected(id: String, isMarked: Bool) {
print("\(id) is marked: \(isMarked)")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Selectable Circle, Customizable
struct SelectableCircle: View {
#Binding var isSelected: Bool
var selectionColor: Color = Color.green
var size: CGFloat = 20
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 10, style: .circular)
.stroke(Color.gray, lineWidth: 2)
.background(isSelected ? selectionColor : Color.clear)
.frame(width: size, height: size, alignment: .center)
.clipShape(Circle())
.onTapGesture {
withAnimation {
isSelected.toggle()
}
}
}
}
}
You can use like this:
struct CircleChooseView_Previews: PreviewProvider {
struct CircleChooseView: View {
#State var checked = false
var body: some View {
HStack {
SelectableCircle(isSelected: $checked)
Text("Item that needs to be selected")
}
}
}
static var previews: some View {
CircleChooseView()
}
}
You should take a look to this post, it's awesome!
https://medium.com/better-programming/how-to-create-and-animate-checkboxes-in-swiftui-e428fe7cc9c1