firstly I am really new to iOS development and Swift (2 weeks coming here from PHP :))
I am trying to build a iOS application that has a side menu. And my intention is when I click on a item in the menu the new view will appear on screen like the 'HomeViewController' and for each consequent item like example1, 2 etc (In place of the menu button, Note I will be adding the top nav bar soon to open the menu)
I am wondering how I can accomplish this feature?
Thanks
ContentView.swift
import SwiftUI
struct MenuItem: Identifiable {
var id = UUID()
let text: String
}
func controllView(clickedview:String) {
print(clickedview)
}
struct MenuContent: View{
let items: [MenuItem] = [
MenuItem(text: "Home"),
MenuItem(text: "Example1"),
MenuItem(text: "Example2"),
MenuItem(text: "Example3")
]
var body: some View {
ZStack {
Color(UIColor(red: 33/255.0, green: 33/255.0, blue: 33/255.0, alpha: 1))
VStack(alignment: .leading, spacing: 0) {
ForEach(items) {items in
HStack {
Text(items.text)
.bold()
.font(.system(size: 20))
.multilineTextAlignment(/*#START_MENU_TOKEN#*/.leading/*#END_MENU_TOKEN#*/)
.foregroundColor(Color.white)
Spacer()
}
.onTapGesture {
controllView(clickedview: items.text)
}
.padding()
Divider()
}
Spacer()
}
.padding(.top, 40)
}
}
}
struct SideMenu: View {
let width: CGFloat
let menuOpen: Bool
let toggleMenu: () -> Void
var body: some View {
ZStack {
//Dimmed backgroud
GeometryReader { _ in
EmptyView()
}
.background(Color.gray.opacity(0.15))
.opacity(self.menuOpen ? 1 : 0)
.animation(Animation.easeIn.delay(0.25))
.onTapGesture {
self.toggleMenu()
}
//Menucontent
HStack {
MenuContent()
.frame(width: width)
.offset(x: menuOpen ? 0 : -width)
.animation(.default)
Spacer()
}
}
}
}
struct ContentView: View {
#State var menuOpen = false
var body: some View {
let drag = DragGesture()
.onEnded {
if $0.translation.width < -100 {
if menuOpen {
withAnimation {
print("Left")
menuOpen.toggle()
}
}
}
if $0.translation.width > -100 {
if !menuOpen {
withAnimation {
print("Right")
menuOpen.toggle()
}
}
}
}
ZStack {
if !menuOpen {
Button(action: {
self.menuOpen.toggle()
}, label: {
Text("Open Menu")
.bold()
.foregroundColor(Color.white)
.frame(width: 200, height: 50, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
.background(Color(.systemBlue))
})
}
SideMenu(width: UIScreen.main.bounds.width/1.6, menuOpen: menuOpen, toggleMenu: toggleMenu)
}
.edgesIgnoringSafeArea(.all)
.gesture(drag)
}
func toggleMenu(){
menuOpen.toggle()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The on tap command from the above code:
.onTapGesture {
controllView(clickedview: items.text)
}
HomeViewController.swift
import UIKit
import WebKit
class HomeViewController: UIViewController, WKNavigationDelegate {
var webView: WKWebView!
override func loadView() {
webView = WKWebView()
webView.navigationDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://developer.apple.com")!
webView.load(URLRequest(url: url))
}
}
You'll need a couple of ingredients:
A way to store the state of the currently active view
A way to communicate the state between your menu and main content view
For the first one, I made an enum that listed the different types of views (ViewType) and added it to your MenuItem model.
For the second, you can pass state via a #Binding from parent to child views and back up the chain.
struct MenuItem: Identifiable {
var id = UUID()
let text: String
let viewType : ViewType
}
enum ViewType {
case home, example1, example2, example3
}
struct MenuContent: View{
#Binding var activeView : ViewType
let items: [MenuItem] = [
MenuItem(text: "Home", viewType: .home),
MenuItem(text: "Example1", viewType: .example1),
MenuItem(text: "Example2", viewType: .example2),
MenuItem(text: "Example3", viewType: .example3)
]
var body: some View {
ZStack {
Color(UIColor(red: 33/255.0, green: 33/255.0, blue: 33/255.0, alpha: 1))
VStack(alignment: .leading, spacing: 0) {
ForEach(items) { item in
HStack {
Text(item.text)
.bold()
.font(.system(size: 20))
.multilineTextAlignment(.leading)
.foregroundColor(Color.white)
Spacer()
}
.onTapGesture {
activeView = item.viewType
}
.padding()
Divider()
}
Spacer()
}
.padding(.top, 40)
}
}
}
struct SideMenu: View {
let width: CGFloat
let menuOpen: Bool
let toggleMenu: () -> Void
#Binding var activeView : ViewType
var body: some View {
ZStack {
//Dimmed backgroud
GeometryReader { _ in
EmptyView()
}
.background(Color.gray.opacity(0.15))
.opacity(self.menuOpen ? 1 : 0)
.animation(Animation.easeIn.delay(0.25))
.onTapGesture {
self.toggleMenu()
}
//Menucontent
HStack {
MenuContent(activeView: $activeView)
.frame(width: width)
.offset(x: menuOpen ? 0 : -width)
.animation(.default)
Spacer()
}
}
}
}
struct ContentView: View {
#State private var menuOpen = false
#State private var activeView : ViewType = .home
var body: some View {
let drag = DragGesture()
.onEnded {
if $0.translation.width < -100 {
if menuOpen {
withAnimation {
print("Left")
menuOpen.toggle()
}
}
}
if $0.translation.width > -100 {
if !menuOpen {
withAnimation {
print("Right")
menuOpen.toggle()
}
}
}
}
ZStack {
VStack {
if !menuOpen {
Button(action: {
self.menuOpen.toggle()
}, label: {
Text("Open Menu")
.bold()
.foregroundColor(Color.white)
.frame(width: 200, height: 50, alignment: .center)
.background(Color(.systemBlue))
})
}
switch activeView {
case .home:
HomeViewControllerRepresented()
case .example1:
Text("Example1")
case .example2:
Text("Example2")
case .example3:
Text("Example3")
}
}
SideMenu(width: UIScreen.main.bounds.width/1.6,
menuOpen: menuOpen,
toggleMenu: toggleMenu,
activeView: $activeView)
.edgesIgnoringSafeArea(.all)
}
.gesture(drag)
}
func toggleMenu(){
menuOpen.toggle()
}
}
struct HomeViewControllerRepresented : UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> HomeViewController {
HomeViewController()
}
func updateUIViewController(_ uiViewController: HomeViewController, context: Context) {
}
}
Related
I'm learning SwiftUI over break for fun over youtube, trying to build a fun little app to learn stuff and i'm attempting to make a custom tab bar at the bottom to control views, and have objects (i made a Person object) that i owuld like to be able to access and modify throughout all my views. From what I can tell, I've achieved that, as it runs perfectly well as I expect it to on the Simulator, but when I try to run on my iPhone I get the error "Thread 1: EXC_BAD_ACCESS (code=257, address=0x481bdfee88082008)"
I'm not familiar with reading memory addresses, i'm just trying to screw around with building apps for fun with my downtime
myTestApp.swift
import SwiftUI
#main
struct myTestApp: App {
var testPerson = Person(name: "Stinky", age: 69)
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(testPerson)
}
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
#State var selectedIndex = 0
let icons = [
"house", "person", "doc", "dice", "gear"
]
let tabNames = [
"Home", "People", "Overview", "Activities", "Settings"
]
var body: some View {
VStack {
ZStack {
switch selectedIndex {
case 0: HomeView()
case 1: PeopleView()
case 2: OverView()
case 3: ActivitiesView()
case 4: SettingsView()
default: HomeView()
}
}
Divider()
.frame(height: 2.0)
HStack {
ForEach(0..<5, id: \.self) { i in
Spacer()
VStack {
Image(systemName: icons[i])
.frame(height: 20.0)
.font(.system(size:23))
.foregroundColor(self.selectedIndex == i ? Color("SelectedColor") : Color("AccentColor"))
Text("\(tabNames[i])")
.font(.system(size:10, weight: .medium, design: .default))
.foregroundColor(self.selectedIndex == i ? Color("SelectedColor") : Color("AccentColor"))
.padding(.top, 1.0)
}
.onTapGesture {
selectedIndex = i
}
.frame(width: 70.0, height: 60.0)
Spacer()
}
}
.frame(height: 41.0)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Views.swift
import SwiftUI
struct HomeView: View {
#EnvironmentObject var testPerson: Person
var body: some View {
NavigationView {
VStack {
ScrollView {
VStack {
Text(testPerson.name + " \(testPerson.age)")
ForEach(0..<5) { i in
Text("The Train Has Arrived!")
.padding(.all, 3.0)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.cornerRadius(20)
.background(Color.gray)
}
Button(action: {
testPerson.age += 1
}, label: {
Text("Age: \(testPerson.age)")
.font(.system(size: 20))
.foregroundColor(.white)
.frame(maxWidth: .infinity)
})
.frame(maxWidth: .infinity, maxHeight: 50)
.foregroundColor(.blue)
.background(.blue)
.cornerRadius(15)
.padding(.horizontal)
}
.navigationTitle("Age: \(testPerson.age)")
}
}
}
struct PeopleView: View {
#EnvironmentObject var testPerson: Person
#State var newPersonCreation = false
var body: some View {
NavigationView {
VStack {
ZStack {
Spacer().fullScreenCover(isPresented: $newPersonCreation, content: {
Button("ass", action: {
self.newPersonCreation.toggle()
})
})
}
Text("Age: \(testPerson.age)")
}
.navigationTitle("People")
.toolbar {
Button(action: {
self.newPersonCreation.toggle()
}, label: {
Image(systemName: "plus")
.foregroundColor(.white)
})
}
}
}
}
struct OverView: View {
var body: some View {
NavigationView {
VStack {
}
.navigationTitle("Overview")
}
}
}
struct ActivitiesView : View {
var body: some View {
NavigationView {
VStack {
}
.navigationTitle("Activities")
}
}
}
struct SettingsView: View {
var body: some View {
NavigationView {
VStack {
}
.navigationTitle("Settings")
}
}
}
struct Previews_Views_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Person.swift
import SwiftUI
class Person: ObservableObject {
#Published var name = "Anonymous"
#Published var age = 1
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
I have sub-navigation inside Listview and trying to achieve leftSlide and rightSlide animation but it always shows default slide animation. I even tried to add .transition(.move(edge: . leading)) & .transition(.move(edge: . trailing))
import SwiftUI
import Combine
struct GameTabView: View {
#State var selectedTab: Int = 0
init() {
UITableView.appearance().sectionHeaderTopPadding = 0
}
var body: some View {
listView
.ignoresSafeArea()
}
var listView: some View {
List {
Group {
Color.gray.frame(height: 400)
sectionView
}
.listRowInsets(EdgeInsets())
}
.listStyle(.plain)
}
var sectionView: some View {
Section {
tabContentView
.transition(.move(edge: . leading)) // NOT WORKING
.background(Color.blue)
} header: {
headerView
}
}
private var headerView: some View {
ScrollViewReader { proxy in
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) {
Button {
withAnimation {
selectedTab = 0
}
} label: {
Text("AAAA")
.padding()
}
Button {
withAnimation {
selectedTab = 1
}
} label: {
Text("BBBB")
.padding()
}
Button {
withAnimation {
selectedTab = 2
}
} label: {
Text("BBBB")
.padding()
}
}
}
}
.background(Color.green)
}
#ViewBuilder private var tabContentView: some View {
switch selectedTab {
case 0:
DummyScreen(title: "FIRST", color: .red)
case 1:
DummyScreen(title: "SECOND", color: .green)
case 2:
DummyScreen(title: "THIRD", color: .blue)
default:
EmptyView()
}
}
}
struct DummyScreen: View {
let title: String
let color: Color
var body: some View {
VStack {
ForEach(0..<15, id: \.self) { index in
HStack {
Text("#\(index): title \(title)")
.foregroundColor(Color.black)
.font(.system(size: 30))
.padding(.vertical, 20)
Spacer()
}
.background(Color.yellow)
}
}
.background(color)
}
}
Just like Asperi said, you can change to another type of View to make the transition works.
I tried your code, changed List to VStack with ScrollView inside to wrap around the tabContentView, and the result of the UI showed the same except with a proper animation now, and you don't have to manually adjust the height of your contents since HStack height is dynamic based on your Text() growth.
Edited: header fixed, animation fixed
import SwiftUI
import SwiftUITrackableScrollView //Added
import Combine
struct GameTabView: View {
#State private var scrollViewContentOffset = CGFloat(0) //Added
#State var selectedTab: Int = 0
init() {
UITableView.appearance().sectionHeaderTopPadding = 0
}
var body: some View {
listView
.ignoresSafeArea()
}
var listView: some View {
ZStack { //Added
TrackableScrollView(.vertical, showIndicators: true, contentOffset: $scrollViewContentOffset) {
VStack {
Color.gray.frame(height: 400)
sectionView
}
}
if(scrollViewContentOffset > 400) {
VStack {
headerView
Spacer()
}
}
}
}
var sectionView: some View {
Section {
tabContentView
.transition(.scale) // FIXED
.background(Color.blue)
} header: {
headerView
}
}
private var headerView: some View {
ScrollViewReader { proxy in
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) {
Button {
withAnimation {
selectedTab = 0
}
} label: {
Text("AAAA")
.padding()
}
Button {
withAnimation {
selectedTab = 1
}
} label: {
Text("BBBB")
.padding()
}
Button {
withAnimation {
selectedTab = 2
}
} label: {
Text("BBBB")
.padding()
}
}
}
}
.background(Color.green)
}
#ViewBuilder private var tabContentView: some View {
switch selectedTab {
case 0:
DummyScreen(title: "FIRST", color: .red)
case 1:
DummyScreen(title: "SECOND", color: .green)
case 2:
DummyScreen(title: "THIRD", color: .blue)
default:
EmptyView()
}
}
}
struct DummyScreen: View {
let title: String
let color: Color
var body: some View {
VStack {
ForEach(0..<15, id: \.self) { index in
HStack {
Text("#\(index): title \(title)")
.foregroundColor(Color.black)
.font(.system(size: 30))
.padding(.vertical, 20)
Spacer()
}
.background(Color.yellow)
}
}
.background(color)
}
}
Thanks to #tail and #Asperi
I finally got the solution via updating ScrollView with ScrollviewProxy:
import SwiftUI
import Combine
struct GameTabView: View {
#State var selectedTab: Int = 0
#State var proxy: ScrollViewProxy?
init() {
UITableView.appearance().sectionHeaderTopPadding = 0
}
var body: some View {
listView
.ignoresSafeArea()
}
var listView: some View {
List {
Group {
Color.gray.frame(height: 400)
sectionView
}
.listRowInsets(EdgeInsets())
}
.listStyle(.plain)
}
var sectionView: some View {
Section {
tabContentView
} header: {
headerView
}
}
private var headerView: some View {
ScrollViewReader { proxy in
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 16) {
Button {
withAnimation {
selectedTab = 0
self.proxy?.scrollTo(0, anchor: .center)
}
} label: {
Text("AAAA")
.padding()
}
Button {
withAnimation {
selectedTab = 1
self.proxy?.scrollTo(1, anchor: .center)
}
} label: {
Text("BBBB")
.padding()
}
Button {
withAnimation {
selectedTab = 2
self.proxy?.scrollTo(2, anchor: .center)
}
} label: {
Text("BBBB")
.padding()
}
}
}
}
.background(Color.green)
}
#ViewBuilder private var tabContentView: some View {
ScrollViewReader { proxy in
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 0) {
ForEach(0..<3, id: \.self) { i in
DummyScreen(title: "Name: \(i)", color: .blue)
.frame(idealWidth: UIScreen.main.bounds.width)
.id(i)
}
}
}
.onAppear {
self.proxy = proxy
}
}
}
}
I have tried to simulate how displaying the call view on the top of all. But I have some unclear points:
Is there any way to make it more simple?
Will the view hierarchy protected for that cases?
AppView.swift
import SwiftUI
struct AppView: View {
#ObservedObject var callVM: CallViewModel
init() {
self.callVM = CallViewModel()
}
var body: some View {
VStack {
IncomingCallView(rootView: appView, isActive: self.$callVM.isIncomingCallActive)
TabView {
TabOne()
.tabItem {
Image(systemName: "list.dash")
Text("Menu")
}
TabTwo()
.tabItem {
Image(systemName: "square.and.pencil")
Text("Order")
}
}
}
.onAppear(perform: load)
}
var appView: some View {
Text("")
}
func load() {
self.callVM.getCall()
}
}
struct AppView_Previews: PreviewProvider {
static var previews: some View {
AppView()
}
}
CallViewModel.swift
import Foundation
class CallViewModel: ObservableObject {
#Published public var isIncomingCallActive = Bool()
init() {
self.isIncomingCallActive = false
}
func getCall() {
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
self.isIncomingCallActive = true
}
}
}
IncomingCallView.swift
import SwiftUI
struct IncomingCallView<RootView: View>: View {
private let rootView: RootView
#Binding var isActive: Bool
init(rootView: RootView, isActive: Binding<Bool>) {
self.rootView = rootView
self._isActive = isActive
}
var body: some View {
rootView
.background(Activator(isActive: $isActive))
}
struct Activator: UIViewRepresentable {
#Binding var isActive: Bool
#State private var myWindow: UIWindow? = nil
func makeUIView(context: Context) -> UIView {
let view = UIView()
DispatchQueue.main.async {
self.myWindow = view.window
}
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
guard let holder = myWindow?.rootViewController?.view else { return }
if isActive && context.coordinator.controller == nil {
context.coordinator.controller = UIHostingController(rootView: loadingView)
let view = context.coordinator.controller!.view
view?.backgroundColor = UIColor.black.withAlphaComponent(1)
view?.isUserInteractionEnabled = true
view?.translatesAutoresizingMaskIntoConstraints = false
holder.addSubview(view!)
holder.isUserInteractionEnabled = true
view?.leadingAnchor.constraint(equalTo: holder.leadingAnchor).isActive = true
view?.trailingAnchor.constraint(equalTo: holder.trailingAnchor).isActive = true
view?.topAnchor.constraint(equalTo: holder.topAnchor).isActive = true
view?.bottomAnchor.constraint(equalTo: holder.bottomAnchor).isActive = true
} else if !isActive {
context.coordinator.controller?.view.removeFromSuperview()
context.coordinator.controller = nil
holder.isUserInteractionEnabled = true
}
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
class Coordinator {
var controller: UIViewController? = nil
}
private var loadingView: some View {
HStack(spacing: 20) {
Button(action: {
print("acceptCall pressed")
// change UI for accepted call
}) {
Image("acceptCall")
.resizable()
.frame(width: 50, height: 50, alignment: .center)
.aspectRatio(contentMode: .fit)
}
Button(action: {
// close the view
print("rejectCall pressed")
self.isActive = false
}) {
Image("rejectCall")
.resizable()
.frame(width: 50, height: 50, alignment: .center)
.aspectRatio(contentMode: .fit)
}
}
.frame(width: 300, height: 300)
.background(Color.primary.opacity(0.7))
.cornerRadius(10)
}
}
}
Something in just swiftUI would look like this
struct AppView: View {
#StateObject var callVM = CallViewModel()
var body: some View {
ZStack {
TabView {
Text("TabOne")
.tabItem {
Image(systemName: "list.dash")
Text("Menu")
}
Text("TabTwo")
.tabItem {
Image(systemName: "square.and.pencil")
Text("Order")
}
}
if callVM.isIncomingCallActive {
ZStack {
Color.green
VStack {
Text("Incoming call...")
Button(action: { callVM.isIncomingCallActive = false }) {
Text("Cancel")
}
}
}.edgesIgnoringSafeArea(.all) // <= here
}
}
.onAppear {
self.callVM.getCall()
}
}
}
class CallViewModel: ObservableObject {
#Published public var isIncomingCallActive = false
func getCall() {
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
self.isIncomingCallActive = true
}
}
}
I am trying to create pagination in SwiftUI with a List but it's not behaving correctly when scrolled and it's size changes.
Here is the code which I am using for it.
class ViewModel: ObservableObject {
#Published var feeds:[String] = []
init() {
for i in 0..<10{
feeds.append("Page \(i)")
}
}
func appentNewData() -> Void{
for i in self.feeds.count..<(self.feeds.count + 10){
feeds.append("New Page \(i)")
}
}
}
struct ContentView: View {
#ObservedObject var viewModel = ViewModel()
#State var isExpanded:Bool = false
var body: some View {
VStack(spacing:0){
Button(action: {
withAnimation {
self.isExpanded.toggle()
}
}) {
Text("Search Bar ( Tap to expand )")
.font(.title)
.foregroundColor(Color.black)
}
.frame(height: self.isExpanded ? 200 : 100)
.frame(maxWidth:.infinity)
.background(Color.red)
GeometryReader { (proxy) in
List{
ForEach(self.viewModel.feeds, id:\.self) { feed in
FeedView(text: feed, size: proxy.size)
.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
.frame(width:proxy.size.width, height: proxy.size.height)
.border(Color.green ,width: 5.0)
.onAppear {
guard let lastItem = self.viewModel.feeds.last else{
return
}
if lastItem == feed{
self.viewModel.appentNewData()
}
}
}
}
.frame(width:proxy.size.width, height: proxy.size.height)
.background(Color.purple)
.onAppear {
UITableViewCell.appearance().selectionStyle = .none
UITableView.appearance().separatorStyle = .none
UITableView.appearance().isPagingEnabled = true
UITableView.appearance().separatorStyle = .none
UITableView.appearance().showsVerticalScrollIndicator = false
}.onDisappear {
UITableView.appearance().isPagingEnabled = false
UITableView.appearance().showsVerticalScrollIndicator = true
}
}
}.clipped()
}
}
struct FeedView:View {
#State var text:String
var size:CGSize
var body: some View{
VStack(spacing: 20.0){
Text(text)
.font(.largeTitle)
HStack{
Text("Width : \(size.width)")
.font(.headline)
Text("Height : \(size.height)")
.font(.headline)
}
}
.frame(maxWidth:.infinity,maxHeight: .infinity,alignment: .center)
}
}
It behaves like this.
Not sure why it is not dividing the row equally since each row has the same width and height.
Here is the quick project if anyone would like to try out.
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)
}
}
}
}
}
}