Custom UI Elements not updating in LazyVGrid - swiftui

I have created a custom element I am calling Slide. I have a LazyVGrid that is displaying my Slide elements. My issue is that when I update the data array that my grid is using, the Slide elements are not updating.
Scenario:
User clicks on the options button on a Slide and changes the Slide color, I am then updating the data array but the Slide element doesn't update despite the data being correct (I have verified this by adding Text(slide.color) into the LazyVGrid which displays the newly set color as expected)
My Suspicions:
I am assuming it doesn't update because I have something wrong in the Slide struct, I am quite new to SwiftUI so I am learning as I go. I must also mention that this loads and displays correctly when I first display the view, the only issue is that it doesn't update when I update the SlideStructure.
Here is the Code:
struct ShowSongFile : View {
#EnvironmentObject var SlideStructure : SlidesModel
#State var selectedSlide : SplaySlide? = nil
var columns = [GridItem(.adaptive(minimum: 320))]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, alignment: .trailing, spacing: 20) {
ForEach(SlideStructure.SongSlides, id:\.id) { slide in
if slide.id == selectedSlide?.id {
Slide(IsSelected:true, SlideData: slide)
} else {
Slide(IsSelected:false, SlideData: slide)
.onTapGesture {
selectSlide(Slide: slide)
}
}
}
}.onAppear(perform: loadSelectedFile)
}
}
Here is the Slide File which also shows how I am setting the colours for the slides:
struct Slide : View {
#State var EnableSlideEditing : Bool? = false
#State var IsSelected : Bool = false
#State var SlideData : SplaySlide
#EnvironmentObject var SlideStructure : SlidesModel
var body : some View {
VStack {
Group{
VStack(alignment: .center) {
let editor = TextEditor(text: $SlideData.lyric)
.multilineTextAlignment(.center)
.padding()
let text = Text(SlideData.lyric)
.multilineTextAlignment(.center)
.frame(width: 320.0, height: 160.0)
if EnableSlideEditing ?? false {
editor
} else {
text
}
Group {
HStack {
Text(SlideData.slideType)
.padding(.leading, 5.0)
Spacer()
MenuButton(label: Image(systemName: "ellipsis.circle")) {
Button("Edit Slide Text", action: {EnableSlideEditing?.toggle()})
Divider()
Menu("Slide Type") {
Button("Verse", action: {SlideType(Type: "Verse", ColorHex: "#f57242")})
Button("Chorus", action: {SlideType(Type: "Chorus", ColorHex: "#0068bd")})
Button("Pre-Chorus", action: {SlideType(Type: "Pre-Chorus", ColorHex: "#02ad96")})
Button("Tag", action: {SlideType(Type: "Tag", ColorHex: "#ad027d")})
Button("Bridge", action: {SlideType(Type: "Bridge", ColorHex: "#02ad96")})
}
Menu("Transitions") {
Button("Option 1", action: {})
Button("Option 2", action: {})
}
Divider()
Button("Delete Slide", action: {})
Button("Duplicate Slide", action: {})
}
.menuButtonStyle(BorderlessButtonMenuButtonStyle())
.frame(alignment: .trailing)
.padding(.trailing, 5.0)
.buttonStyle(PlainButtonStyle())
}
}
.frame(width: 320, height: 20, alignment: .leading)
.background(Color.init(hex: SlideData.slideBorderColorHex))
}
}
.frame(width: 320, height: 180, alignment: .bottomLeading)
.background(IsSelected ? Color.accentColor : .black)
.cornerRadius(10)
}
}
func SlideType(Type:String, ColorHex: String) {
for (index, slide) in SlideStructure.SongSlides.enumerated() {
if slide.id == self.SlideData.id {
SlideStructure.SongSlides[index].slideBorderColorHex = ColorHex
SlideStructure.SongSlides[index].slideType = Type
ShowSongFile.main?.SongFile.slides = SlideStructure.SongSlides
ShowSongFile.main!.SongFile.SaveSongToDisk()
}
}
}
}
SlideStructure: (SlideModel)
class SlidesModel : ObservableObject {
#Published var SongSlides : [SplaySlide] = []
}

There are too many missing parts to be able to test any particular solution, so I will take a guess.
In ShowSongFile you could try :
LazyVGrid(columns: columns, alignment: .trailing, spacing: 20) {
ForEach(SlideStructure.SongSlides, id:\.id) { slide in
Slide(SlideData: slide) // <--- here
.onTapGesture {
selectSlide(Slide: slide)
}
.background(slide.id == selectedSlide?.id ? Color.accentColor : .black) // <--- here
}
and adjust Slide accordingly, that is, remove IsSelected and .background(IsSelected ? Color.accentColor : .black).
P.S: your naming and case of your variables and functions makes
reading your code less than appealing.

Related

SwiftUI - Custom DatePicker - Show layer or backgorund on top of all or how to do it as DatePicker

I'm trying two write a custom Datepicker so that I can modify the button style. As far as I know, SwiftUI doesn't allow to modify it. My base design based on this answer that I've already improved it.
struct CustomDatePickerView: View {
#State private var showPicker = false
#State var selectedText: String
#State var selectedDateLocal : Date = Date()
var body: some View {
VStack {
Button {
withAnimation {
showPicker.toggle()
}
} label: {
Text(selectedText)
.padding()
.padding(.horizontal)
.foregroundColor(.black)
.background(
RoundedRectangle( cornerRadius:10, style: .continuous).fill(Color.yellow.opacity(0.2))
)
.overlay(
RoundedRectangle(cornerRadius: 10, style: .continuous)
.strokeBorder(Color.black, lineWidth: 1)
)
.id(selectedText)
}
.background(
DatePicker("", selection: $selectedDateLocal, in: closedRange, displayedComponents: [.date,.hourAndMinute])
.datePickerStyle(.graphical)
.frame(width: 400, height: 400)
.clipped()
.background(Color.yellow.opacity(0.1).cornerRadius(10))
.opacity(showPicker ? 1 : 0 )
.offset(x: 0, y: 230)
).onChange(of: selectedDateLocal) { newValue in
let format = DateFormatter()
format.timeStyle = .none
format.dateStyle = .short
print("Name changed to \(format.string(from: newValue))!")
selectedText = format.string(from: newValue)
withAnimation {
showPicker.toggle()
}
}
}
}
}
It was working excellent on test view (sorry image upload failure).
When it is placed on the real application, the background/overlay was behind of other views.
I cannot change the Z-level since the UI a bit complicated.
How can we show the DatePicker displayed on background/overlay on top of everything as the DatePicker does? What is the proper way to do this?

Dropdown menu button SwiftUI

I'm trying to implement such dropdown menu https://imgur.com/a/3KcKhv4 but could do it like that https://imgur.com/67bKU5Q
The problem is that selected option doesn't have to repeated. Could you please help me how can I do dropdown menu like in design?
class MenuViewModel: ObservableObject {
#Published var selectedOption: String = "За все время"
}
struct DropdDown: View {
let buttons = ["За все время", "За день", "За неделю"]
#ObservedObject var viewModel = MenuViewModel()
#State var expanded: Bool = false
var body: some View {
VStack(spacing: 30) {
Button {
self.expanded.toggle()
} label: {
Text(viewModel.selectedOption)
.fontWeight(.bold)
.foregroundColor(Color.black)
Spacer()
Image(systemName: "chevron.down")
.foregroundColor(Color.white)
}
if expanded {
ForEach(self.buttons, id: \.self) { buttonTitle in
VStack(alignment: .leading, spacing: 5) {
Button {
self.expanded.toggle()
viewModel.selectedOption = buttonTitle
} label: {
Text(buttonTitle)
.padding(10)
}
.foregroundColor(Color.black)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
.padding()
.frame(width: 300)
.background(Color.gray)
.cornerRadius(10)
}
}
struct DropdDown_Previews: PreviewProvider {
static var previews: some View {
DropdDown()
}
}
Just create computed property array in DropdDown View for store buttons without selectedOption
var availableButtons: [String] {
return buttons.filter { $0 != viewModel.selectedOption }
}
And use in ForEach loop instead buttons array
ForEach(self.availableButtons, id: \.self) {}

Why the scrollview doesn't get updated with new data from array?

I'm trying to send and then display them in the scrollview realtime. But nothing shows up. How to solve it? So, basically when the user types the message into a textbox then it will be saved in array and then it will be populated to the crollView in realtime so the user can view all the messages.
Error: No errors, it just isn't visible.
import SwiftUI
struct SingleMessageBubbleModel: Identifiable {
let id = UUID()
var text: String
var received: Bool
var timeStamp: Date
}
var messagesDBArray : [SingleMessageBubbleModel] = []
struct ContentView: View {
#State private var showOnTheSpotMessaging: Bool = true
#State var textTyped: String
var body: some View {
if (showOnTheSpotMessaging) {
VStack {
HStack {
ScrollViewReader { proxy in
ScrollView {
LazyVStack {
ForEach(messagesDBArray, id: \.id) { message in
MessageBubble(message: message)
}
}
}
.padding(.top, 10)
.background(.gray)
.onChange(of: messagesDBArray.count) { id in
withAnimation {
proxy.scrollTo(id, anchor: .bottom)
}
}
}
.frame( height: 200, alignment: .bottomLeading)
}
HStack () {
TextEditor (text: $textTyped)
.frame(width: 200, height: 200, alignment: .leading)
Button ("Send", action: {
messagesDBArray.append(SingleMessageBubbleModel(text: textTyped, received: true, timeStamp: Date()))
})
}
}
}
}
}
struct MessageBubble: View {
var message: SingleMessageBubbleModel
#State private var showTime = false
var body: some View {
VStack(alignment: message.received ? .leading : .trailing) {
HStack {
Text(message.text)
.padding()
.background(message.received ? Color.gray : Color.blue)
.cornerRadius(30)
}
.frame(maxWidth: 300, alignment: message.received ? .leading : .trailing)
.onTapGesture {
withAnimation {
showTime.toggle()
}
}
if showTime {
Text("\(message.timeStamp.formatted(.dateTime.hour().minute()))")
.font(.caption2)
.foregroundColor(.gray)
.padding(message.received ? .leading : .trailing, 25)
}
}
.frame(maxWidth: .infinity, alignment: message.received ? .leading : .trailing)
.padding(message.received ? .leading : .trailing)
.padding(.horizontal, 4)
}
}
Basically, when the button is pressed, your property messagesDBArray is well and truly append with the new value.
However, and it's really important to understand this point in swiftUI, nothing triggers the refresh of the view.
I suggest you two solutions:
If you don't need messagesDBArray to be outside of ContentView:
You just have to add messagesDBArray as a state in ContentView like following
struct ContentView: View {
#State var messagesDBArray : [SingleMessageBubbleModel] = []
#State private var showOnTheSpotMessaging: Bool = true
#State var textTyped: String = ""
var body: some View {
if (showOnTheSpotMessaging) {
VStack {
HStack {
ScrollViewReader { proxy in
ScrollView {
LazyVStack {
ForEach(messagesDBArray, id: \.id) { message in
MessageBubble(message: message)
}
}
}
.padding(.top, 10)
.background(.gray)
.onChange(of: messagesDBArray.count) { id in
withAnimation {
proxy.scrollTo(id, anchor: .bottom)
}
}
}
.frame( height: 200, alignment: .bottomLeading)
}
HStack () {
TextEditor (text: $textTyped)
.frame(width: 200, height: 200, alignment: .leading)
Button ("Send", action: {
messagesDBArray.append(SingleMessageBubbleModel(text: textTyped, received: true, timeStamp: Date()))
})
}
}
}
}
}
If you need messagesDBArray to be outside of ContentView:
1- Create a class (ViewModel or Service or whatever you wan to call it) with messagesDBArray as a #Published property
final class ViewModel: ObservableObject {
#Published var messagesDBArray : [SingleMessageBubbleModel] = []
}
2- Observe this class in ContentView in order to append and receive the update
struct ContentView: View {
#ObservedObject private var viewModel = ViewModel()
#State private var showOnTheSpotMessaging: Bool = true
#State var textTyped: String = ""
var body: some View {
if (showOnTheSpotMessaging) {
VStack {
HStack {
ScrollViewReader { proxy in
ScrollView {
LazyVStack {
ForEach(viewModel.messagesDBArray, id: \.id) { message in
MessageBubble(message: message)
}
}
}
.padding(.top, 10)
.background(.gray)
.onChange(of: viewModel.messagesDBArray.count) { id in
withAnimation {
proxy.scrollTo(id, anchor: .bottom)
}
}
}
.frame( height: 200, alignment: .bottomLeading)
}
HStack () {
TextEditor (text: $textTyped)
.frame(width: 200, height: 200, alignment: .leading)
Button ("Send", action: {
viewModel.messagesDBArray.append(SingleMessageBubbleModel(text: textTyped, received: true, timeStamp: Date()))
})
}
}
}
}
}
I hope that this is clear to you and that it has been useful 😉

Navigation Bar buttons not working after sheet dismissed. UINavigation wrapper?

I am working on a SwiftUI app, and I have a NavigationView with some buttons in the navigation bar. The problem is that after dismissing a full-page sheet (triggered by a button, not in the navigation bar), those buttons stop working.
I've tried the following, but some TestFlight users say the problem persists (I can't reproduce it myself):
Add an id to each button and change it after the dismiss (I even added it to the toolbar) to force a repaint
Add a height to buttons and navbar
Add #Environment(\.presentationMode) var presentationMode: Binding<PresentationMode> to the presenting and presented views
Set the navigation bar title display mode to inline
I saw an answer on a similar post suggesting wrapping the navigation in a UINavigation. But how do you go about that? I have wrapped views (UITextView), but do you need to wrap the controller? or the navigationItem? or just the buttons. The answer didn't elaborate.
It only seems to happen when the sheet is presented by a button outside the navigation bar. The buttons in the navigation bar also present sheets and they cause no issues. I'm tempted to just hide the navbar altogether and fake it with a regular view.
Just in case you want to see what I have, here's the relevant code in my presenting view (I removed some unrelated content and functionality):
struct PListView: View {
//https://stackoverflow.com/questions/58837007/multiple-sheetispresented-doesnt-work-in-swiftui
enum ActiveSheetProjectList: Identifiable {
case help, settings
var id: Int {
hashValue
}
}
enum ActiveFullSheetProjectList: Identifiable {
case addProject, quickCount
var id: Int {
hashValue
}
}
#ObservedObject var viewModel : ProjectListViewModel
#State var presentingDeleteProjectSheet = false
#State var itemsToDelete : [UUID]?
#State var activeSheet: ActiveSheetProjectList?
#State var activeFullSheet : ActiveFullSheetProjectList?
#ObservedObject var settings : Settings
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
init(settings: Settings) {
self.viewModel = ProjectListViewModel()
self.settings = settings
//https://medium.com/#francisco.gindre/customizing-swiftui-navigation-bar-8369d42b8805
// this is not the same as manipulating the proxy directly
let appearance = UINavigationBarAppearance()
// this overrides everything you have set up earlier.
appearance.configureWithTransparentBackground()
appearance.backgroundColor = UIColor(Color.navBar)
// this only applies to big titles
appearance.largeTitleTextAttributes = [
.font : UIFont.systemFont(ofSize: 20),
NSAttributedString.Key.foregroundColor : UIColor(Color.smallTextMain)
]
// this only applies to small titles
appearance.titleTextAttributes = [
.font : UIFont.systemFont(ofSize: 20),
NSAttributedString.Key.foregroundColor : UIColor(Color.smallTextMain)
]
//In the following two lines you make sure that you apply the style for good
UINavigationBar.appearance().scrollEdgeAppearance = appearance
UINavigationBar.appearance().standardAppearance = appearance
// This property is not present on the UINavigationBarAppearance
// object for some reason and you have to leave it til the end
UINavigationBar.appearance().tintColor = UIColor(Color.smallTextMain)
}
var body: some View {
NavigationView {
ZStack {
Color.background.edgesIgnoringSafeArea(.all)
VStack {
List {
ForEach(projects) { project in
NavigationLink(destination: ProjectView(project: project, settings: settings, viewModel: ProjectListViewModel(), viewContext: viewContext)
.environmentObject(self.settings)
{
HStack {
Text(project.name ?? "").font(.headline).padding(.bottom, 5).padding(.top, 5)
}
}
.listRowInsets(.init(top: 10, leading: 3, bottom: 10, trailing: 3))
.accessibilityHint(Text(NSLocalizedString("View project details", comment: "")))
}
.onDelete(perform: { indexSet in
presentingDeleteProjectSheet = true
itemsToDelete = indexSet.map { projects[$0].id! }
})
.listRowBackground(Color.lightGray)
.padding(0)
.actionSheet(isPresented: $presentingDeleteProjectSheet) {
var name = NSLocalizedString("Project", comment: "Generic label")
if let id = itemsToDelete?.first {
name = projects.first(where: {$0.id == id})?.name ?? ""
}
return ActionSheet(title: Text(NSLocalizedString(String.localizedStringWithFormat("Delete %#", name), comment: "alert title")), message: Text(NSLocalizedString("Deleting a project can't be undone", comment: "Deleting alert message")), buttons: [
.destructive(Text(NSLocalizedString("Delete", comment: "Button label"))) {
if itemsToDelete != nil {
viewModel.deleteProjects(projects: activeProjectsDateCreated, ids: itemsToDelete!)
}
},
.cancel({itemsToDelete?.removeAll()})
])
}
}
.padding(0)
.onAppear(perform: {
UITableView.appearance().backgroundColor = UIColor(Color.lightGray)
UITableViewCell.appearance().selectionStyle = .none
})
}
}
}
.fullScreenCover(item: $activeFullSheet, content: { item in
switch item {
case .quickCount :
// THIS IS THE SHEET THAT CAUSES THE ISSUES
QuickCountView(viewModel: CounterViewModel(counter: viewModel.getScratchCounter(projects: quickCountProject), sound: settings.sound, showTotal: settings.showTotal, viewContext: viewContext))
.environmentObject(settings)
case .addProject:
// No problems after dismissing this one
AddEditProjectView(viewModel: AddEditProjectViewModel(project : nil, startAt: settings.startNumber, viewContext: viewContext), isNew: true, isEditing: .constant(true))
.environmentObject(settings)
}
})
Button(action: { activeFullSheet = .quickCount }, label: {
Text(NSLocalizedString("Quick Count +", comment: "Button label"))
.accessibilityLabel(NSLocalizedString("Quick count", comment: ""))
})
.buttonStyle(CustomButton(style: .button, size: .large))
.padding()
.sheet(item: $activeSheet) { item in
switch item {
case .help:
HelpView()
case .settings:
SettingsView()
.environmentObject(settings)
}
}
}
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItemGroup(placement: .navigationBarLeading) {
HStack {
Button(action: {
self.activeSheet = .settings
}) {
Image(systemName: "gearshape.fill")
.font(Font.system(size: 28, weight: .medium, design: .rounded))
.foregroundColor(Color.main)
.accessibilityLabel(Text(NSLocalizedString("Settings", comment: "a11y label")))
.frame(height: 96, alignment: .trailing)
}
Button(action: {
self.activeSheet = .help
}) {
Image(systemName: "questionmark")
.font(Font.system(size: 28, weight: .semibold, design: .rounded))
.foregroundColor(Color.main)
.accessibilityLabel(Text(NSLocalizedString("Help", comment: "a11y label")))
.frame(height: 96, alignment: .trailing)
}
}
}
ToolbarItemGroup(placement: .navigationBarTrailing) {
HStack {
Button(action: { activeFullSheet = .addProject }) {
Image(systemName: "plus")
.font(Font.system(size: 30, weight: .semibold))
.foregroundColor(Color.main)
.accessibilityLabel(Text(NSLocalizedString("Add a Project", comment: "a11y label")))
.frame(height: 96, alignment: .trailing)
}
Button(action: {
self.isEditing.toggle()
}) {
Image(systemName: isEditing ? "xmark" : "pencil")
.font(Font.system(size: 28, weight: .black))
.foregroundColor(activeProjectsDateCreated.count >= 1 ? Color.main : Color.gray)
.accessibilityLabel(Text(NSLocalizedString("Edit Project List", comment: "a11y label")))
.frame(height: 96, alignment: .trailing)
}.disabled(activeProjectsDateCreated.count < 1)
.frame(height: 96, alignment: .trailing)
}
}
}
}
}

How to create Radiobuttons in SwiftUI?

I would like to react on a choice of a user. Something similar to this example:
In a 2nd stage would I like to show additional content below each radiobutton, e.g. moving the buttons 2 and 3 from each other in order to give a list of websites for allowing.
So far I haven't found how to do this in SwiftUI.
Many thanks in advance!
Picker(selection: $order.avocadoStyle, label: Text("Avocado:")) {
Text("Sliced").tag(AvocadoStyle.sliced)
Text("Mashed").tag(AvocadoStyle.mashed)
}.pickerStyle(RadioGroupPickerStyle())
This is the code from the 2019 swiftUI essentials keynote (SwiftUI Essentials - WWDC 2019. Around 43 minutes in the video they show this example.
It will look like this:
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()
}
}
I just edited #LizJ answer , by adding Binding instead of didTapActive & didTapInactive , so like that it will looks like other SwiftUI elements
import SwiftUI
struct RadioButton: View {
#Binding var checked: Bool //the variable that determines if its checked
var body: some View {
Group{
if checked {
ZStack{
Circle()
.fill(Color.blue)
.frame(width: 20, height: 20)
Circle()
.fill(Color.white)
.frame(width: 8, height: 8)
}.onTapGesture {self.checked = false}
} else {
Circle()
.fill(Color.white)
.frame(width: 20, height: 20)
.overlay(Circle().stroke(Color.gray, lineWidth: 1))
.onTapGesture {self.checked = true}
}
}
}
}
I'm using swift4, Catalina OS and Xcode 11.2 and was having the issue where RadioGroupPickerStyle was unavailable for iOS and .radiogroup just didn't work (it froze in build) so I made my own that's reusable for other occasions. (notice its only the button so you have to handle the logic yourself.) Hope it helps!
import SwiftUI
struct RadioButton: View {
let ifVariable: Bool //the variable that determines if its checked
let onTapToActive: ()-> Void//action when taped to activate
let onTapToInactive: ()-> Void //action when taped to inactivate
var body: some View {
Group{
if ifVariable {
ZStack{
Circle()
.fill(Color.blue)
.frame(width: 20, height: 20)
Circle()
.fill(Color.white)
.frame(width: 8, height: 8)
}.onTapGesture {self.onTapToInactive()}
} else {
Circle()
.fill(Color.white)
.frame(width: 20, height: 20)
.overlay(Circle().stroke(Color.gray, lineWidth: 1))
.onTapGesture {self.onTapToActive()}
}
}
}
}
TO USE: Put this in any file and you can use it as you would any other view anywhere else in the project. (we keep a global folder that has a buttons file in it)
I will use the previous answer of #LizJ and i will add a text after the radio button to resemble (RadioListTile in Flutter)
struct RadioButton: View {
let ifVariable: Bool //the variable that determines if its checked
let radioTitle: String
var onTapToActive: ()-> Void//action when taped to activate
let onTapToInactive: ()-> Void //action when taped to inactivate
var body: some View {
Group{
if ifVariable {
HStack(alignment: .center, spacing: 16) {
ZStack{
Circle()
.fill(AppColors.primaryColor)
.frame(width: 20, height: 20)
Circle()
.fill(Color.white)
.frame(width: 8, height: 8)
}.onTapGesture {self.onTapToInactive()}
Text(radioTitle)
.font(.headline)
}
} else {
HStack(alignment: .center, spacing: 16){
Circle()
.fill(Color.white)
.frame(width: 20, height: 20)
.overlay(Circle().stroke(Color.gray, lineWidth: 1))
.onTapGesture {self.onTapToActive()}
Text(radioTitle)
.font(.headline)
}
}
}
}
I will also provide an example for the selection logic
we will create a enum for radio cases
enum PaymentMethod: Int {
case undefined = 0
case credit = 1
case cash = 2
}
then we will create #State variable to carry the selection, i will not recreate another SwiftUI view but only explain the basic concept without any boilerplate code
struct YourView: View {
#State private var paymentMethod: PaymentMethod
var body: some View {
RadioButton(ifVariable: paymentMethod == PaymentMethod.credit,radioTitle: "Pay in Credit", onTapToActive: {
paymentMethod = .credit
}, onTapToInactive: {})
RadioButton(ifVariable: paymentMethod == PaymentMethod.cash,radioTitle: "Pay in Cash", onTapToActive: {
paymentMethod = .cash
}, onTapToInactive: {})
}
}
with this previous code you can toggle between radio buttons in SwiftUI with a text after each selection to resemble (RadioListTile in Flutter)