I have a view with a filter bar. One says "Shirts" & the other says "Trousers". When "Shirts" is selected, it displays a grid view of shirts. When "Trousers" is selected in the filter bar, it shows a grid view of trousers.
When I tap on an item (a shirt or a pair of trousers) it takes me to a different view (showing the product in more detail). However, the filter bar remains. I need to hide the filter bar when an item is tapped and it takes the user to a detail view.
I need to hide the marketplacefilterbar view when a product is tapped.
My ViewModel for the filter bar is:
enum MarketplaceFilterViewModel: Int, CaseIterable {
case shirts
case trousers
var title: String {
switch self {
case .shirts: return "Shirts"
case .trousers: return "Trousers"
}
}
}
Some code from the MarketplaceViewModel:
class MarketplaceViewModel: NSObject, ObservableObject {
#Published var search = ""
// Shirt Data...
#Published var shirts: [Shirt] = []
#Published var filteredShirt: [Shirt] = []
// Trouser Data...
#Published var trousers: [Trouser] = []
#Published var filteredTrouser: [Trouser] = []
}
Code for the marketplace view is:
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 10){
marketplaceFilterBar
if selectedMarketplaceFilter == .raise {
MarketplaceShirtView()
}
if selectedMarketplaceFilter == .save {
MarketplaceTrouserView()
}
}
}
var marketplaceFilterBar: some View {
VStack {
HStack(spacing: 15){
Image("Logo")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 60.0, height: 60.0)
.padding(EdgeInsets.init(top: 0, leading: 45, bottom: 0, trailing: 0))
Spacer(minLength: 0)
}
.padding([.horizontal,.top])
HStack {
ForEach(MarketplaceFilterViewModel.allCases, id: \.rawValue) { item in
VStack {
Text(item.title)
.font(.headline)
.fontWeight(selectedMarketplaceFilter == item ? .semibold: .regular)
.foregroundColor(selectedMarketplaceFilter == item ? .black: .gray)
if selectedMarketplaceFilter == item {
Capsule()
.foregroundColor(Color("LightBlue"))
.frame(height: 3)
.matchedGeometryEffect(id: "filter", in: animation)
} else {
Capsule()
.foregroundColor(Color(.clear))
.frame(height: 3)
}
}
.onTapGesture {
withAnimation(.easeInOut) {
self.selectedMarketplaceFilter = item
}
}
}
}
.overlay(Divider().offset(x: 0, y: 16))
}
}
Code for the Shirt Detail View:
struct ShirtDetailView: View {
#Binding var shirtData : Shirt!
#Binding var showDetailShirt: Bool
#Namespace var animation: Namespace.ID
// Shared Data Model...
#EnvironmentObject var sharedData: SharedDataModel
#EnvironmentObject var marketplaceData: MarketplaceViewModel
ScrollView {
VStack{
HStack {
Button(action: {
withAnimation(.easeOut){showDetailShirt.toggle()}
}) {
Image(systemName: "arrow.backward.circle.fill")
.foregroundColor(.gray)
.frame(width: 40, height: 40)
.background(Color.white,in: RoundedRectangle(cornerRadius: 10, style: .continuous))
.shadow(color: .black.opacity(0.1), radius: 5, x: 5, y: 5)
}
Text(shirtData.shirt_name)
.font(.largeTitle)
.fontWeight(.heavy)
.foregroundColor(.black)
}
VStack(spacing: 10){
WebImage(url: URL(string: shirtData.shirt_image))
.resizable()
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.all)
.cornerRadius(15)
Text("Details")
.opacity(0.7)
.frame(maxWidth: .infinity,alignment: .leading)
.padding(.bottom)
HStack(spacing: 15) {
Text(shirtData.shirt_details)
.font(.system(size: 18))
.padding()
}
}
}
}
Code for the Trouser Detail View:
struct TrouserDetailView: View {
#Binding var trouserData : Trouser!
#Binding var showDetailTrouser: Bool
#Namespace var animation: Namespace.ID
// Shared Data Model...
#EnvironmentObject var sharedData: SharedDataModel
#EnvironmentObject var marketplaceData: MarketplaceViewModel
ScrollView {
VStack{
HStack {
Button(action: {
withAnimation(.easeOut){showDetailTrouser.toggle()}
}) {
Image(systemName: "arrow.backward.circle.fill")
.foregroundColor(.gray)
.frame(width: 40, height: 40)
.background(Color.white,in: RoundedRectangle(cornerRadius: 10, style: .continuous))
.shadow(color: .black.opacity(0.1), radius: 5, x: 5, y: 5)
}
Text(trouserData.trouser_name)
.font(.largeTitle)
.fontWeight(.heavy)
.foregroundColor(.black)
}
VStack(spacing: 10){
WebImage(url: URL(string: trouserData.trouser_image))
.resizable()
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.all)
.cornerRadius(15)
Text("Details")
.opacity(0.7)
.frame(maxWidth: .infinity,alignment: .leading)
.padding(.bottom)
HStack(spacing: 15) {
Text(trouserData.trouser_details)
.font(.system(size: 18))
.padding()
}
}
}
}
Code for the SharedDataModel:
class SharedDataModel: ObservableObject {
// Detail Shirt Data...
#Published var detailShirt : Shirt?
#Published var showDetailShirt : Bool = false
// matched Geoemtry Effect from Search page...
#Published var fromSearchPage: Bool = false
}
The updated code for marketplace:
#Binding var charityData : Charity!
#Binding var showDetailCharity: Bool
#Binding var businessData : Business!
#Binding var showDetailBusiness: Bool
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 10){
if !showDetailCharity && !showDetailBusiness {
//MARK: This is the filter bar - the view for this is further down the page
marketplaceFilterBar
}
if selectedMarketplaceFilter == .raise {
MarketplaceRaiseView()
}
if selectedMarketplaceFilter == .save {
MarketplaceSaveView()
}
}
The code for TabView:
#StateObject var appModel: AppViewModel = .init()
#StateObject var sharedData: SharedDataModel = SharedDataModel()
// Animation Namespace...
#Namespace var animation
// Hiding Tab Bar...
init(){
UITabBar.appearance().isHidden = true
}
var body: some View {
VStack(spacing: 0){
// Tab View...
TabView(selection: $appModel.currentTab) {
Marketplace(animation: _animation)
.environmentObject(sharedData)
.tag(Tab.Market)
.setUpTab()
Home()
.environmentObject(sharedData)
.tag(Tab.Home)
.setUpTab()
}
Since you already have showDetailShirt and showDetailTrouser, you can use them to hide the marketplacefilterbar like so:
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 10){
if !showDetailShirt && !showDetailTrouser {
marketplaceFilterBar
}
if selectedMarketplaceFilter == .raise {
MarketplaceShirtView()
}
if selectedMarketplaceFilter == .save {
MarketplaceTrouserView()
}
}
}
Related
Given code is for custom bottom tab-bar with plus button in center by using we need to hide and show one detail view and from last tab we are just opens menu from bottom sheet.
Problem :- When we are hide and show a detail view using center button that refresh the ui and redirect us on root from any subview. while we are using bottom sheet that works fine for us.
When we are open bottom sheet in subview that is start view from top of tabbar.
Problem Video
import SwiftUI
struct DashboardTBV: View {
#StateObject var manager = CalendarManager()
#StateObject var viewRouter: ViewRouter = ViewRouter()
#State var showTimerDetail : Bool = false
#State var showToast : Bool = false
#State var toastMsg : String = ""
let layout = [
GridItem(.flexible())
]
#State var showWorkOrderList : Bool = false
#State var month : String = ""
#State var year : String = ""
#State var filterDate : String = ""
#State var spacing: CGFloat = 20
#State var headspace: CGFloat = 15
#State var sidesScaling: CGFloat = 0.8
#State var isWrap: Bool = false
#State var autoScroll: Bool = false
#State var time: TimeInterval = 1
#State var currentIndex: Int = 0
#State var isLoading : Bool = true
#State var popToRoot : Bool = false
#State var showSheet : Bool = false
#State var showProfile : Bool = false
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0) {
Spacer()
ZStack(alignment: .bottom) {
VStack {
}
.frame(width: geometry.size.width, height: geometry.size.height)
.background(Constant.AppColors.dark_background.opacity((showTimerDetail) ? 0.3 : 0))
.zIndex(5)
.onTapGesture {
withAnimation {
self.showTimerDetail = false
}
}
VStack(spacing:0) {
switch viewRouter.currentPage {
case .home:
VStack {
Spacer()
Text("Home")
Spacer()
}
case .setting:
VStack {
Spacer()
Text("Liked")
Spacer()
}
case .notification:
VStack {
Spacer()
Text("Records")
Spacer()
}
case .profile:
Text("")
Spacer()
}
HStack {
DashboardTabBarIcon(viewRouter: viewRouter, assignedPage: .home, width: geometry.size.width/5, height: geometry.size.height/28, systemIconName: "home", tabName: "")
DashboardTabBarIcon(viewRouter: viewRouter, assignedPage: .setting, width: geometry.size.width/5, height: geometry.size.height/28, systemIconName: "Time-Circle", tabName: "")
ZStack {
}
.frame(width: geometry.size.width/8, height: geometry.size.width/8)
.clipShape(Circle())
.offset(y: -55)
.onTapGesture {
withAnimation {
showTimerDetail.toggle()
}
}
DashboardTabBarIcon(viewRouter: viewRouter, assignedPage: .notification, width: geometry.size.width/5, height: geometry.size.height/28, systemIconName: "Chat", tabName: "")
DashboardTabBarIcon(viewRouter: viewRouter, assignedPage: .profile, width: geometry.size.width/5, height: geometry.size.height/28, systemIconName: "Category", tabName: "xyz") {
self.showSheet = true
}
}
.frame(width: geometry.size.width, height: 110)
.riseShadow()
.zIndex(10)
}
if showTimerDetail {
VStack {
HStack {
Spacer()
Text("Time Clock")
.foregroundColor(Constant.AppColors.iPoint_orange)
.font(.custom(Constant.Font.Biotif_Medium, size: 20))
Spacer()
}.padding()
Divider()
.background(Constant.AppColors.white)
.frame(height: 0.6)
HStack {
Spacer()
Text("Clocked In at: 8/25/2022 9:41:52 AM")
.foregroundColor(Constant.AppColors.white)
.font(.custom(Constant.Font.Biotif_Regular, size: 16))
Spacer()
}.padding()
Button {
withAnimation {
}
} label: {
HStack {
Text("Clock Out")
.font(.custom(Constant.Font.Biotif_SemiBold, size: 16))
.foregroundColor(Constant.AppColors.dark_background)
}
.frame(maxWidth: .infinity)
.frame(height: 50)
}
.frame(maxWidth: .infinity)
.background(Constant.AppColors.white)
.cornerRadius(20)
.padding()
}
.padding(.bottom, 30)
.background(Constant.AppColors.dark_background)
.cornerRadius(12)
.padding([.leading, .trailing])
.padding(.bottom, 110)
.transition(AnyTransition.opacity.animation(.easeInOut(duration: 0.3)))
.zIndex(6)
}
ZStack {
Image(systemName: "plus")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geometry.size.width/15, height: geometry.size.width/15)
.foregroundColor( self.showTimerDetail ? Constant.AppColors.iPoint_orange : Constant.AppColors.white)
.rotationEffect(.degrees(self.showTimerDetail ? 180.0 : 0.0))
}
.frame(width: geometry.size.width/8, height: geometry.size.width/8)
.background(Constant.AppColors.dark_background)
.clipShape(Circle())
.offset(y: -85)
.onTapGesture {
withAnimation {
showTimerDetail.toggle()
}
}
.zIndex(100)
}
PushView(destination: ProfileView().edgesIgnoringSafeArea(.all), destinationId: "ProfileView", isActive: $showProfile) {
Text("")
.frame(width: 0, height: 0)
}
}
.modifier(MenuBottomSheet(showClose: false , isVisible: $showSheet))
}
}
}
struct DashboardTabBarIcon: View {
#StateObject var viewRouter: ViewRouter
let assignedPage: Page
let width, height: CGFloat
let systemIconName, tabName: String
var tapAction : (()->())?
var body: some View {
VStack {
Image(systemIconName)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: width, height: height)
.padding(.top, 30)
Spacer()
}
.padding(.horizontal, -4)
.onTapGesture {
if tabName == "" {
viewRouter.currentPage = assignedPage
} else {
tapAction?()
}
}
.foregroundColor(viewRouter.currentPage == assignedPage ? .selectedTabBarIconColor : .unSelectedTabBarIconColor)
}
}
When ever I click on textField and key board appears, my list of data i.e coming from API is vanashing,
import SwiftUI
import ExytePopupView
struct Wallet: View {
#State private var searchText = ""
#State private var showCancelButton: Bool = false
#ObservedObject var walltetVM = ShopViewModel()
#State var showShopDetails: Bool = false
#State var openShowDetails: Bool = false
#State var selectedShopId:String = ""
#State var selectedCouponDetail: CouponModel?
var body: some View {
NavigationView {
VStack {
// Search view
VStack{
HStack {
Button {
} label: {
Image(systemName: "magnifyingglass")
.foregroundColor(AppColors.semiBlue)
.frame(width: 20, height: 20, alignment: .center)
.padding()
}
ZStack(alignment: .leading) {
if searchText.isEmpty {
Text(MaggnetLocalizedString(key: "Restaurant, Beauty shop...", comment: ""))
.foregroundColor(AppColors.blackWhite)
.font(Font(AppFont.lightFont(lightFontWithSize: 15)))
}
TextField("", text: $searchText)
.font(Font(AppFont.regularFont(regularFontWithSize: 15)))
}
.foregroundColor(AppColors.blackWhite)
.padding(.horizontal,10)
.padding(.leading,-15)
Divider()
Button {
} label: {
HStack {
Image("places")
}
.padding(.horizontal,20)
.padding(.leading,-12)
}
}
.frame(height: 45)
.background(AppColors.fadeBackground)
.clipShape(Capsule())
}
.padding(.horizontal)
.padding(.vertical,10)
ScrollView(.vertical) {
VStack{
NavigationLink(destination:ShopDetail(shopId:self.selectedShopId)
.environmentObject(walltetVM),
isActive: $openShowDetails) {
EmptyView()
}.hidden()
Points()
ForEach(0..<walltetVM.finalsCouponList.count,id: \.self){
index in
VStack{
// SHOP LIST HEADERS
HStack{
Text(walltetVM.finalsCouponList[index].name)
.multilineTextAlignment(.leading)
.font(Font(AppFont.mediumFont(mediumFontWithSize: 15)))
.foregroundColor(AppColors.blackWhite)
.padding(.horizontal,10)
Spacer()
Button {
} label: {
Text(MaggnetLocalizedString(key: "viewAll", comment: ""))
.font(Font(AppFont.regularFont(regularFontWithSize: 12)))
.foregroundColor(AppColors.blackWhite)
.padding()
.frame(height: 27, alignment: .center)
.background(AppColors.fadeBackground)
.cornerRadius(8)
}
}
.padding()
// MAIN SHOP LIST
VStack{
ScrollView(.horizontal,showsIndicators: false){
HStack{
ForEach(0..<walltetVM.finalsCouponList[index].couopons.count,id: \.self){
indeX in
Shops(coupons: walltetVM.finalsCouponList[index].couopons[indeX])
.onTapGesture {
selectedShopId = walltetVM.finalsCouponList[index].couopons[indeX].businessId?.description ?? ""
print(selectedShopId)
selectedCouponDetail = walltetVM.finalsCouponList[index].couopons[indeX]
showShopDetails = true
}
}
}
.padding(.horizontal)
}
}
.padding(.top,-5)
}
.padding(.top,-5)
}
}
}
.blur(radius: showShopDetails ? 3 : 0)
.popup(isPresented: $showShopDetails, autohideIn: 15, dismissCallback: {
showShopDetails = false
}) {
ShopDetailPopUp(couponDeatil: self.selectedCouponDetail)
.frame(width: 300, height: 400)
}
.navigationBarTitle(Text("Wallet"),displayMode: .inline)
.navigationBarItems(trailing: HStack{
Button {
} label: {
Image("wishIcon").frame(width: 20, height: 20, alignment: .center)
}
Button {
} label: {
Image("notifIcon").frame(width: 20, height: 20, alignment: .center)
}
})
.resignKeyboardOnDragGesture()
}
.onAppear(){
walltetVM.getWallets()
}
.onReceive(NotificationCenter.default.publisher(for: NSNotification.openShopDetails))
{ obj in
showShopDetails = false
openShowDetails.toggle()
}
.environment(\.layoutDirection,Preferences.chooseLanguage == AppConstants.arabic ? .rightToLeft : .leftToRight)
}
}
}
struct Wallet_Previews: PreviewProvider {
static var previews: some View {
Wallet()
}
}
bannerList is marked as #Published, API call working fine, but in same View class I have one search text field , when I tap on it all the data I was rendering from API get lost and disappears from list.
You are holding a reference to your view model using ObservedObject:
#ObservedObject var walltetVM = ShopViewModel()
However, because you are creating the view model within the view, the view might tear down and recreate this at any moment, which might be why you are losing your data periodically.
If you use StateObject, this ensures that SwiftUI will retain the object for as long as the view lives, so the object won't be recreated.
#StateObject var walltetVM = ShopViewModel()
Alternatively, you could create the view model outside of the view and inject it into the view and keep using ObservedObject (but you'll still need to make sure the object lives as long as the view).
I found something working but still not optimised solution
instead of calling it on onAppear, I called API function in init method.
init(){
walltetVM.getWallets()
}
this bug has been scratching my head for the past few days and I still don't know why the problem is arising and what the fix is. I have a camera screen and integrated it with the TOCropViewController (https://github.com/TimOliver/TOCropViewController) to allow a user to select a picture from their photo library and crop it to show a new post. For some reason the image picker is detecting that it should change the view to the ImagePicker from the camera view screen but it's not displaying it on ios14.4 and below but it works just fine for iOS 14.5 and above.
Here is my camera view code:
struct CameraView: View {
#StateObject var model = CameraModel()
#State var currentZoomFactor: CGFloat = 1.0
#StateObject var registerData = RegisterViewModel()
#StateObject var newPostData = NewPostModel()
enum SheetType {
case imagePick
case imageCrop
case share
}
#State private var currentSheet: SheetType = .imagePick
#State private var actionSheetIsPresented = false
#State private var sheetIsPresented = false
#State private var originalImage: UIImage?
#State private var image: UIImage?
#State private var croppingStyle = CropViewCroppingStyle.default
#State private var croppedRect = CGRect.zero
#State private var croppedAngle = 0
#StateObject var userData = UserViewModel()
var captureButton: some View {
Button(action: {
let impactMed = UIImpactFeedbackGenerator(style: .light)
impactMed.impactOccurred()
model.capturePhoto()
}, label: {
Circle()
.foregroundColor(.white)
.frame(width: 80, height: 80, alignment: .center)
.overlay(
Circle()
.stroke(Color.black.opacity(0.8), lineWidth: 2)
.frame(width: 65, height: 65, alignment: .center)
)
})
}
var capturedPhotoThumbnail: some View {
Group {
RoundedRectangle(cornerRadius: 10)
.frame(width: 55, height: 55, alignment: .center)
.foregroundColor(Color.gray.opacity(0.2))
.onTapGesture(perform: {
// newPostData.picker.toggle()
self.croppingStyle = .default
self.currentSheet = .imagePick
self.sheetIsPresented = true
print("HERE11 and \(self.currentSheet) and \(self.sheetIsPresented)")
})
.overlay(
Image("gallery")
.renderingMode(.template)
.resizable()
.frame(width: 25, height: 25)
.foregroundColor(Color("white")))
//CODE WITH BUG on ios 14.4 and below. I tried a regular sheet as well that works on another view in ios 14.4 but it doesn't work in the cameraview()
.sheet(isPresented: $sheetIsPresented) {
if (self.currentSheet == .imagePick) {
ImagePickerView(croppingStyle: self.croppingStyle, sourceType: .photoLibrary, onCanceled: {
// on cancel
}) { (image) in
guard let image = image else {
return
}
self.originalImage = image
DispatchQueue.main.async {
self.currentSheet = .imageCrop
self.sheetIsPresented = true
}
}
} else if (self.currentSheet == .imageCrop) {
ZStack {
Color("imagecropcolor").edgesIgnoringSafeArea(.all)
ImageCropView(croppingStyle: self.croppingStyle, originalImage: self.originalImage!, onCanceled: {
// on cancel
}) { (image, cropRect, angle) in
// on success
self.image = image
model.resetPhoto()
newPostData.newPost.toggle()
}
}
}
}
}
}
var flipCameraButton: some View {
Button(action: {
let impactMed = UIImpactFeedbackGenerator(style: .light)
impactMed.impactOccurred()
model.flipCamera()
}, label: {
Circle()
.foregroundColor(Color.gray.opacity(0.2))
.frame(width: 45, height: 45, alignment: .center)
.overlay(
Image(systemName: "camera.rotate.fill")
.foregroundColor(.white))
})
}
var body: some View {
GeometryReader { reader in
ZStack {
Color.black.edgesIgnoringSafeArea(.all)
VStack {
HStack{
Button(action: {
model.switchFlash()
}, label: {
Image(systemName: model.isFlashOn ? "bolt.fill" : "bolt.slash.fill")
.font(.system(size: 20, weight: .medium, design: .default))
})
.accentColor(model.isFlashOn ? .yellow : .white)
.padding(.leading, 30)
Spacer()
if model.photo != nil {
Text("taken photo").onAppear{
newPostData.newPost.toggle()
}
}
// Image(uiImage: model.photo.image!)
// .resizable()
// .aspectRatio(contentMode: .fill)
// .frame(width: 60, height: 60)
// .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
// .animation(.spring())
//
}
CameraPreview(session: model.session)
.gesture(
DragGesture().onChanged({ (val) in
// Only accept vertical drag
if abs(val.translation.height) > abs(val.translation.width) {
// Get the percentage of vertical screen space covered by drag
let percentage: CGFloat = -(val.translation.height / reader.size.height)
// Calculate new zoom factor
let calc = currentZoomFactor + percentage
// Limit zoom factor to a maximum of 5x and a minimum of 1x
let zoomFactor: CGFloat = min(max(calc, 1), 5)
// Store the newly calculated zoom factor
currentZoomFactor = zoomFactor
// Sets the zoom factor to the capture device session
model.zoom(with: zoomFactor)
}
})
)
.onAppear {
model.configure()
}
.alert(isPresented: $model.showAlertError, content: {
Alert(title: Text(model.alertError.title), message: Text(model.alertError.message), dismissButton: .default(Text(model.alertError.primaryButtonTitle), action: {
model.alertError.primaryAction?()
}))
})
.overlay(
Group {
if model.willCapturePhoto {
Color.black
}
}
)
.animation(.easeInOut)
HStack {
capturedPhotoThumbnail
Spacer()
captureButton
.padding(.trailing, 10)
Spacer()
flipCameraButton
}
.padding(.horizontal, 20)
.padding(.bottom, 20)
}
}.fullScreenCover(isPresented: $newPostData.newPost) {
if model.photo == nil {
NewPost(imageData: (self.image?.pngData())! )
} else {
NewPost(imageData: model.photo.originalData)
}
}
}
}
}
Here is where the CameraView() gets called from my Home Screen
import SwiftUI
import Firebase
struct Home: View {
#AppStorage("current_status") var status = false
#AppStorage("showSheet") var showSheet = false
#State var loadedPost = Post(id: 0, PostUID: "", PostName: "", selectedForPost: false, time: Date())
#State var selectedTab = "camera"
var edges = UIApplication.shared.windows.first?.safeAreaInsets
#StateObject var modelData = ModelView()
#StateObject var userData = UserViewModel()
var body: some View {
VStack(spacing: 15){
VStack (spacing: 0) {
GeometryReader{_ in
ZStack{
if selectedTab == "Post"{
Post(loadedPost: $loadedPost, selectedTab: $selectedTab)
}else if selectedTab == "camera"{
CameraView()
}else if selectedTab == "user"{
User(selectedTab: $selectedTab, loadedPost: $loadedPost)
}
}
}.onChange(of: selectedTab) { (_) in
switch(selectedTab){
case "Post": if
!modelData.isPostLoad{modelData.loadPost()}
case "camera": if
!modelData.isCameraLoad{modelData.loadCamera()}
case "user": if
!modelData.isUserLoad{modelData.loadUser()}
default: ()
}
}
//Tabview hide to show friend modal
if !showSheet{
Divider()
HStack(spacing: 0) {
Spacer(minLength: 0)
TabButton(title: "Post", selectedTab: $selectedTab)
Spacer(minLength: 0)
TabButton(title: "camera", selectedTab: $selectedTab)
.padding(.leading, 30)
.padding(.trailing, 30)
Spacer(minLength: 0)
TabButton(title: "user", selectedTab: $selectedTab)
Spacer(minLength: 0)
}
.padding(.horizontal, 30)
.padding(.bottom, edges!.bottom == 0 ? 15 : edges!.bottom)
.background(Color.black)
}
}
.ignoresSafeArea(.all, edges: .bottom)
.background(Color("Black").ignoresSafeArea(.all, edges: .all))
}
}
}
//Tab Button
struct TabButton : View {
var title: String
#Binding var selectedTab: String
var body: some View {
Button(action: {
withAnimation{selectedTab = title}
}) {
VStack(spacing: 5) {
//Top indicator
//Custom shape...
if title == "user" {
Image(title)
.renderingMode(.template)
.resizable()
.foregroundColor(selectedTab == title ? Color.white : Color("Grey"))
.frame(width: 26.5, height: 26.5)
.padding(.top, UIScreen.screenHeight < 500 ? -5 : 15)
}else if title == "camera"{
Image(title)
.renderingMode(.template)
.resizable()
.foregroundColor(selectedTab == title ? Color.white : Color("Grey"))
.frame(width: 40, height: 40)
.padding(.top, UIScreen.screenHeight < 500 ? -5 : 15)
}else{
Image(title)
.renderingMode(.template)
.resizable()
.foregroundColor(selectedTab == title ? Color.white : Color("Grey"))
.frame(width: 32.5, height: 32.5)
.padding(.top, UIScreen.screenHeight < 500 ? -5 : 15)
}
}
}
}
}
//can update with load views here
class ModelView: ObservableObject {
#Published var isPostLoad = false
#Published var isCameraLoad = false
#Published var isUserLoad = false
init() {
//load initial data
isCameraLoad = true
print("Home Data Loaded")
}
func loadPost(){
print("Post Loaded")
isPostLoad = true
}
func loadCamera(){
print("Camera Loaded")
isCameraLoad = true
}
func loadUser(){
print("User loaded")
isUserLoad = true
}
}
I would greatly appreciate any help on how to get the ImagePicker view to show up for iOS 14.1-ios 14.4 I've been scratching my head since I worked on it assuming anything that works on iOS 14.5 and above should work on below but only this specific ImagePicker is not working as intended. Thanks!
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)
}
}
}
}
}
}