how to pass an img value between 2 views? - ios

I am trying to pass a #Binding UI Image from an Image picker to another view through a sheet. I tried to use the OnDismiss() parameter but it did not work, even tried ondisapper(). What i want is that, when i press a button, it should open up the image picker directly. When a user has selected an image it should take that image and navigate to another view and show the image. This is the code im trying but is not working : -
NavigationBar.swift
import SwiftUI
struct NavigationBar: View {
var title = ""
#Binding var hasScrolled: Bool
#State var showUploadPost = false
#State var imagePickerPresented = false
#State private var selectedImage: UIImage?
#State var postImage: Image?
// #State var showAccount = false
#State var captionText = ""
var body: some View {
ZStack {
Color.clear
.background(.ultraThinMaterial)
.blur(radius: 10)
.opacity(hasScrolled ? 1 : 0)
Text(title)
.animatableFont(size: hasScrolled ? 22 : 34, weight: .bold)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.leading, 20)
.padding(.top, 20)
.offset(y: hasScrolled ? -4 : 0)
HStack(spacing: 16) {
Button {
imagePickerPresented = true
} label: {
Image(systemName: "plus.square")
.font(.title3.weight(.bold))
.frame(width: 46, height: 46)
.foregroundColor(.secondary)
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 14, style: .continuous))
.strokeStyle(cornerRadius: 14)
}
.sheet(isPresented: $imagePickerPresented, onDismiss: {
newPostView(image: $selectedImage) // this is the problem
}) {
ImagePicker(image: $selectedImage)
}
.frame(maxWidth: .infinity, alignment: .trailing)
.padding(.trailing, 20)
.padding(.top, 20)
.offset(y: hasScrolled ? -4 : 0)
}
.frame(height: hasScrolled ? 44 : 70)
.frame(maxHeight: .infinity, alignment: .top)
}
}
extension NavigationBar {
func loadImage() {
guard let selectedImage = selectedImage else { return }
postImage = Image(uiImage: selectedImage)
}
}
newPostView.swift
import SwiftUI
struct newPostView: View {
#Binding var image: UIImage?
var body: some View {
VStack {
Text("hi")
}
}
}
ImagePicker.swift
struct ImagePicker: UIViewControllerRepresentable {
#Binding var image: UIImage?
#Environment(\.presentationMode) var mode
func makeUIViewController(context: Context) -> some UIViewController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
return picker
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let image = info[.originalImage] as? UIImage else { return }
self.parent.image = image
self.parent.mode.wrappedValue.dismiss()
}
}
}

Solution 1:
Simply use hidden navigation bar technique to push it to another view on selecting the Image.
struct NavigationBar: View {
#State var imagePickerPresented = false
#State private var selectedImage: UIImage?
#State private var newPost = false
var body: some View {
VStack {
Text("Hello, World!")
Button {
imagePickerPresented = true
} label: {
Image(systemName: "plus.square")
.font(.title3.weight(.bold))
.frame(width: 46, height: 46)
.foregroundColor(.secondary)
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 14, style: .continuous))
}
.sheet(isPresented: $imagePickerPresented, onDismiss: {
newPost.toggle() //1 <--- FIX
}) {
ImagePicker(image: $selectedImage)
}
// 2 FIX ⬇️
// Hide Navigation Link
NavigationLink("NewPostView", isActive: $newPost) {
newPostView(image: $selectedImage)
}.hidden()
}
}
}
Note: This works when the NavigationBar is inside the NavigationView.
For example -
struct ContenView: View {
var body: some View {
NavigationView{
NavigationBar()
}
}
}
Solution 2: If you don't want Navigation to the newPostView, use like this
struct NavigationBar: View {
#State var imagePickerPresented = false
#State private var selectedImage: UIImage?
#State private var newPost = false
var body: some View {
VStack {
Text("Hello, World!")
Button {
imagePickerPresented = true
} label: {
Image(systemName: "plus.square")
.font(.title3.weight(.bold))
.frame(width: 46, height: 46)
.foregroundColor(.secondary)
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 14, style: .continuous))
}
.sheet(isPresented: $imagePickerPresented, onDismiss: {
newPost.toggle() //1 <--- FIX
}) {
ImagePicker(image: $selectedImage)
}
.fullScreenCover(
isPresented: $newPost) {
newPostView(image: $selectedImage)
}
}
}
}
struct ContentView: View {
#State var imagePickerPresented = false
#State private var selectedImage: UIImage?
#State private var newPost = false
var body: some View {
NavigationBar()
}
}

Related

iOS: How to switch views with a Side Menu, SwitftUI

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) {
}
}

How to set a View as top for keyWindow on SwiftUI [duplicate]

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
}
}
}

#Binding value change not being recognised in parent view

I have a connected a #Binding variable called showCodeMessage between my parent view LiveView and its child view NewTimer.
struct LiveView: View {
#ObservedObject var countdown: CountDown
#State var showCodeMessage: Bool = false {
didSet(val){
print("changed: \(val)") // doesn't print
}
}
var body: some View {
ZStack {
NewTimer(countdown: self.countdown, showCodeMessage: $showCodeMessage)
if self.showCodeMessage { // doesn't show
MessageWithButton()
.frame(minWidth: 0, maxWidth: .infinity, alignment: .bottom)
.padding(.top, 10)
.padding(20)
.opacity(self.messageOpacity2)
}
}
}
}
struct NewTimer: View {
#ObservedObject var countdown: CountDown
#Binding var showCodeMessage: Bool
#State var codeSent = false
#State var inBackground = false
var body: some View {
VStack{
Text("Timer")
.onAppear{
self.countdown.secondsLeft = 600
self.countdown.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true){ _ in
self.countdown.secondsLeft -= 1
// At the 9:00 minute mark, show code message
if self.countdown.secondsLeft <= 595 && !showCodeMessage && self.inBackground == false {
print("Show code message: \(self.countdown.secondsLeft)")
self.showCodeMessage = true
}
}
}
}
}
}
However, when I perform self.showCodeMessage = true - the change doesn't register in my parent view. It doesn't even detect that a change has been made.
Any idea why?
EDIT: Added other classes/structs below
class CountDown: ObservableObject {
init(secondsLeft: Int){
self.secondsLeft = secondsLeft
}
#Published var secondsLeft: Int
var timer: Timer?
func start(){
// Reset epoch
UserDefaults.standard.set(0, forKey: "epoch")
}
func stop(){
self.timer?.invalidate()
}
func performAtEnd(action: #escaping () -> Void){
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true){ _ in
self.secondsLeft -= 1
print("performAtEnd() \(self.secondsLeft)")
if (self.secondsLeft == 0){
print("action() \(self.secondsLeft)")
action()
self.timer?.invalidate()
}
}
}
}
struct MessageWithButton: View {
let text: String
let color: Color
let hasButton: Bool = false
#Binding var showCode: Bool
var body: some View {
HStack{
Text("\(self.text)")
.frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
.font(.custom("AvenirNext-Bold", size: 16))
.foregroundColor(.white)
.padding(11)
MessageButton(showCode: $showCode).padding(5)
}
.background(
RoundedRectangle(cornerRadius: 5)
.foregroundColor(self.color)
)
}
}
struct MessageButton : View {
#Binding var showCode: Bool
// #AppStorage("codeResult") var codeResult = UserDefaults.standard.string(forKey: "codeResult") ?? ""
#AppStorage("codeResult") var codeResult = UserDefaults.standard.string(forKey: "codeResult") ?? ""
var body: some View {
Button(action: {self.clickedCode()}){
Text("\(buttonText())")
.font(.custom("AvenirNext-Bold", size: 18))
.fontWeight(.bold)
.foregroundColor(Color.white)
.padding(10)
}
.background(Color("LightBlack"))
.cornerRadius(5)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.black, lineWidth: 1)
)
.padding(.trailing, 5)
}
func buttonText() -> String {
if self.showCode { return "HIDE" }
return "ENTER"
}
func clickedCode(){
// Remove 'incorrect' from codeResult so Code input can show again
self.codeResult = ""
print("clickedcode()")
self.showCode.toggle()
}
}

SwiftUI : Pagination issue in List

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.

SwiftUI: How to implement Radio button in SwiftUI

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)
}
}
}
}
}
}

Resources