I have a view made up of 3 files. This view is a marketplace. There are two options to pick from in the filter tab bar at the top of the view. The view changes in response to these tabs successfully. When an item on the marketplace is tapped, a detailed view of the item is displayed.
However, when the detail view is displayed, the filter tab bar remains at the top of the view. I need this filter tab bar to hide when a detail view variable is set to true.
This is my code:
Marketplace code:
import SwiftUI
struct Marketplace: View {
#StateObject var MarketplaceModel = MarketplaceViewModel()
#State private var selectedMarketplaceFilter: MarketplaceFilterViewModel = .shoe
// MARK: For Smooth Sliding Effect
#Namespace var animation
// Shared Data...
#EnvironmentObject var sharedData: SharedDataModel
#State var shoeData : Shoe = Shoe()
#State var showDetailShoe: Bool = false
#State var sockData : Sock = Sock()
#State var showDetailSock: Bool = false
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 10){
if !showDetailShoe && !showDetailSock {
marketplaceFilterBar
}
if selectedMarketplaceFilter == .shoes {
MarketplaceShoeView()
}
if selectedMarketplaceFilter == .socks {
MarketplaceSockView()
}
}
}
}
//Filter bar view
var marketplaceFilterBar: some View {
VStack {
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("Blue"))
.frame(height: 3)
.matchedGeometryEffect(id: "filter", in: animation)
} else {
Capsule()
.foregroundColor(Color(.clear))
.frame(height: 3)
}
}
.onTapGesture {
withAnimation(.easeInOut) {
self.selectedMarketplaceFilter = item
}
}
}
}
}
}
}
MarketplaceShoeView(same exact code as MarketplaceSockView):
import SwiftUI
struct MarketplaceShoeView: View {
#StateObject var MarketplaceModel = MarketplaceViewModel()
#State private var selectedMarketplaceFilter: MarketplaceFilterViewModel = .shoe
#Namespace var animation
#State var showDetailShoe = false
#State var selectedShoe : Shoe!
// Shared Data...
#EnvironmentObject var sharedData: SharedDataModel
#State var string = ""
var body: some View {
var columns = Array(repeating: GridItem(.flexible()), count: 2)
ZStack{
VStack(spacing: 10){
HStack(spacing: 5) {
HStack(spacing: 12){
Image(systemName: "magnifyingglass")
TextField("Search", text: $MarketplaceModel.search)
}
}
if MarketplaceModel.shoes.isEmpty{
ProgressView()
}
else{
ScrollView(.vertical, showsIndicators: false, content: {
LazyVGrid(columns: Array(repeating: GridItem(.flexible(),spacing: 10), count: 2),spacing: 20){
ForEach(MarketplaceModel.filteredShoe){shoe in
ShoeView(shoeData: shoe)
.onTapGesture {
withAnimation(.easeInOut){
selectedShoe = shoe
showDetailShoe.toggle()
}
}
}
}
})
}
}
if selectedShoe != nil && showDetailShoe{
ShoeDetailView(showDetailShoe: $showDetailShoe, shoeData: selectedShoe, shoe_id: string)
}
}
}
}
ShoeDetailView:
struct ShoeDetailView: View {
#StateObject var MarketplaceModel = MarketplaceViewModel()
#Binding var showDetailShoe: Bool
var shoeData : Shoe
var shoe_id: String
#Namespace var animation: Namespace.ID
#EnvironmentObject var sharedData: SharedDataModel
#EnvironmentObject var marketplaceData: MarketplaceViewModel
#State var string = ""
var body: some View {
NavigationView{
VStack{
VStack{
HStack {
Button(action: {
withAnimation(.easeOut){showDetailShoe.toggle()}
}) {
Image(systemName: "arrow.backward.circle.fill")
ForEach(MarketplaceModel.shoe_details_array){ items in
Text(items.shoe_name)
}
}
}
Image("ShoeImage")
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading, spacing: 15) {
Text(shoeData.shoe_name)
Text(shoeData.shoe_details)
}
}
}
}
}
}
SharedDataModel:
class SharedDataModel: ObservableObject {
#Published var detailSock : Sock?
#Published var showDetailSock : Bool = false
#Published var detailShoe : Shoe?
#Published var showDetailShoe : Bool = false
//Add something here??
}
marketplaceFilterBar needs to be hidden when a detailView is true.
You need to use the boolean values from EnvironmentObject instead of redefining them inside views. As follow:
import SwiftUI
struct Marketplace: View {
...
#EnvironmentObject var sharedData: SharedDataModel
...
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 10){
// Use from env object
if !sharedData.showDetailShoe && !sharedData.showDetailSock {
marketplaceFilterBar
}
...
}
}
}
...
}
and so on...
Related
I am trying to overlay the modal view of a child in the parent's ZStack doing the following:
class ViewModel: ObservableObject {
#Published var showGreen = false
}
struct ContentView: View {
#StateObject var viewModel = ViewModel()
#State var showModal = false
var body: some View {
ZStack {
Color.red
.onTapGesture {
showModal = true
}
.fullScreenCover(isPresented: $showModal) {
ModalView()
.environmentObject(viewModel)
}
if viewModel.showGreen {
Color.green
}
}
}
}
struct ModalView: View {
#EnvironmentObject var viewModel: ViewModel
var body: some View {
ZStack {
Color.blue
}
.onTapGesture {
viewModel.showGreen = true
}
}
}
Using this setup, is there anyway in the parent (ContentView in this case) to show a view that not only covers it's children but also the modally presented view's of it's children?
I know there are ways to do this by overlaying on the children themselves but that is not what I am after.
The easiest way to cover it inside ModalView
struct ModalView: View {
#EnvironmentObject var viewModel: ViewModel
var body: some View {
ZStack {
Color.blue
if viewModel.showGreen {
Color.green
}
}
.onTapGesture {
viewModel.showGreen = true
}
}
}
Another hack is to present one more modal view
ModalView()
.fullScreenCover(isPresented: $viewModel.showGreen) {
Color.green
}
.environmentObject(viewModel)
If you really need to build your cover outside of ModalView, pass it as a parameter
class ViewModel: ObservableObject {
#Published var showGreen = false
}
struct ContentView: View {
#StateObject var viewModel = ViewModel()
#State var showModal = false
var body: some View {
ZStack {
Color.red
.onTapGesture {
showModal = true
}
.fullScreenCover(isPresented: $showModal) {
ModalView(content: {
if viewModel.showGreen {
Color.green
}
})
.environmentObject(viewModel)
}
}
}
}
struct ModalView<Content: View>: View {
#EnvironmentObject var viewModel: ViewModel
#ViewBuilder var content: () -> Content
var body: some View {
ZStack {
Color.blue
content()
}
.onTapGesture {
viewModel.showGreen = true
}
}
}
UPDATE: One more way without modifying ModalView
ModalView()
.overlay(content: {
if viewModel.showGreen {
Color.green
}
})
.environmentObject(viewModel)
If the user clicks on connexionBtnView I want to redirect them to an AdminView or UserView
import SwiftUI
struct ConnexionView: View {
#State var loginId: String = ""
#State var pwd: String = ""
#StateObject private var keyboardHander = KeyBoardHandler()
var body: some View {
NavigationView{
ZStack{
Image("background")
.ignoresSafeArea()
VStack (spacing: 15){
Spacer()
logoView
Spacer()
titleView
loginIdView
loginPwdView
connexionBtnView
Spacer()
NavigationLink {
LostPwdView()
} label: {
lostPwd
}
Spacer()
}.frame(maxHeight: .infinity)
.padding(.bottom,keyboardHander.keyboardHeight)
.animation(.default)
}
}
}
The NavigationLink has the isActive parameter. You can pass it in the init of NavigationLink and when this state variable has the true value you will redirect to another view. Details here.
struct ConnexionView: View {
#State var isActive: Bool = false
var body: some View {
NavigationView {
VStack {
NavigationLink(isActive: $isActive) {
LostPwdView()
} label: {
Text("Some Label")
}
Button("Tap me!") {
isActive = true
}
}
}
}
}
struct LostPwdView: View {
var body: some View {
Text("Hello")
}
}
What you need to do is having a #State variable that would trigger the navigation:
.fullScreenCover(
isPresented: $viewShown
) {
print("View dismissed")
} content: {
NextView()
}
Where NextView() is the View you want to show and the viewShown is your State variable, below a full example:
struct ExampleView: View {
#State var isNextPageOpen = false
var body: some View {
Button("Tap Here To Navigate") {
isNextPageOpen = true
}
.fullScreenCover(
isPresented: $isNextPageOpen
) {
print("View dismissed")
} content: {
NextView()
}
}
}
When I click on the left arrow it should dismiss the view, but only the UI responds as the button being clicked and the view pushed by a NavigationLink is not dismissed...
The same view pushed by another NavigationLink in another view works perfectly, but when pushed by this NavigationLink, I click on the left arrow, only 1 in 20 times it dismisses the view. Is it bug in SwiftUI again? Is there a workaround?
import SwiftUI
import Firebase
import struct Kingfisher.KFImage
struct SearchView: View {
#Environment(\.presentationMode) var presentation
#State var typedSearchValue = ""
#State var createNewPost = false
#State var showMessaging = false
#EnvironmentObject var userInfo : UserData
#Binding var switchTab: Int
#State var text : String = ""
#State var foundUsers: [FetchedUser] = []
#State var showAccount = false
#State var fetchedUser = FetchedUser()
var body: some View {
NavigationView {
ZStack{
// navigate to that user's profile << the problem navlink
NavigationLink(destination:
UserAccountView(fetchedUser: self.$fetchedUser, showAccount: self.$showAccount)
.environmentObject(self.userInfo)
.environmentObject(FetchFFFObservable())
,isActive: self.$showAccount
){
EmptyView()
}.isDetailLink(false)
//...
NavigationLink(destination:
MessagingMainView(showMessaging: self.$showMessaging)
.environmentObject(UserData())
.environmentObject(MainObservable()),
isActive: self.$showMessaging
){
Text("")
}
VStack(spacing: 0) {
SearchBarMsg(text: $text, foundUsers: $foundUsers)
.environmentObject(userInfo)
.padding(.horizontal)
VStack{
if text != ""{
List(foundUsers, id: \.username){ user in
FetchedUserCellView(user: user)
.environmentObject(self.userInfo)
.onTapGesture {
self.fetchedUser = user
self.showAccount = true
}
}
}else{
//....
}
}
}
.navigationBarColor(.titleBarColor)
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
Navigates to this view, and the button in navigationItems doesn't work, although the UI responds:
struct UserAccountView: View {
#Environment(\.presentationMode) var presentation
//...
#Binding var showAccount : Bool
var body: some View {
VStack {
//.....
}
.navigationBarColor(.titleBarColor)
.navigationBarTitle(Text(fetchedUser.username), displayMode: .inline)
.navigationBarItems(leading: Button(action: {
//this button doesn't work!
self.showAccount = false
}, label: {
Image(systemName: "arrow.left")
.resizable()
.frame(width: 20, height: 15)
})).accentColor(.white)
)
}
}
Update: I refactored it a bit to use #ObservableObject, but the issue still persists
In my SwiftUI app, I have a LazyVGrid populated with n cards and one New Event button. When I press the New Event button, I have a custom modal overlaying the View to let the user add a new event. However, after dismissing the view (by tapping a Create Event button), the modal disappears however the new event card isn't added to the LazyVGrid. It only appears once I force quit the app and re-open it.
Here is the main ContentView:
struct ContentView: View {
#State var progress: Double = 0.0
#State var editMode: Bool = false
#State var angle: Double = 0
#State var showModal: Bool = false
#ObservedObject var model: Model
var columns: [GridItem] = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
func animate(while condition: Bool) {
self.angle = condition ? -0.6 : 0
withAnimation(Animation.linear(duration: 0.125).repeatForever(while: condition)) {
self.angle = 0.6
}
if !condition {
self.angle = 0
}
}
var body: some View {
ZStack {
NavigationView {
LazyVGrid(columns: columns, spacing: 10) {
ForEach(model.events, id: \.self) { event in
SmallCardView(event: event, angle: $angle)
}
if !showModal {
AddEventButtonView(
editMode: $editMode,
showModal: $showModal,
namespace: namespace
)
} else {
Spacer().frame(height: 100)
}
}
.onLongPressGesture {
if !self.editMode {
self.editMode = true
animate(while: self.editMode)
}
}
}
if self.showModal {
AddEventView(namespace: namespace, events: self.$model.events, showModal: $showModal)
}
}
}
}
This is the View for the Add Event button:
struct AddEventButtonView: View {
#Binding var editMode: Bool
#Binding var showModal: Bool
var namespace: Namespace.ID
var body: some View {
Image(systemName: "calendar.badge.plus")
.frame(height: 100)
.frame(maxWidth: .infinity)
.opacity(self.editMode ? 0.0 : 1.0)
.onTapGesture {
withAnimation {
self.showModal = true
}
}
}
}
and this is my modal view:
struct AddEventView: View {
#State var eventName: String = ""
#State var endDate = Date()
#State var gradientIndex: Int = 0
#Binding var showModal: Bool
#Binding var events: [Event]
var namespace: Namespace.ID
init(namespace: Namespace.ID, events: Binding<[Event]>, showModal: Binding<Bool>) {
self.namespace = namespace
self._events = events
self._showModal = showModal
}
var body: some View {
VStack(spacing: 30.0) {
//...
Button(action: {
let event = Event(name: eventName, start: .init(), end: endDate, gradientIndex: gradientIndex)
self.events.append(event)
withAnimation {
self.showModal = false
}
}) {
RoundedRectangle(cornerRadius: 13)
.frame(maxWidth: .infinity)
.frame(height: 42)
.overlay(
Text("Add Event")
.foregroundColor(.white)
.bold()
)
}
.disabled(self.eventName.isEmpty)
}
}
}
If anyone has any idea of how to update the LazyVGrid to show the new event card when a new event is added that'd be awesome, thanks!
Here's the problem:
It is not clear how you did set up model, but it should be observed to update view, as (scratchy)
struct ContentView: View {
#State var progress: Double = 0.0
#State var editMode: Bool = false
#State var angle: Double = 0
#State var showModal: Bool = false
#ObservedObject var model: Model // << here !!
...
or use #EnvironmentObject pattern with injection via .environmentObject
or for Swift 2.0 use
#StateObject var model: Model = Model.shared
I have a navigation requirement that looks something like this:
Each detail screen can navigation to the next and previous detail screen. At the same time, the "back" button should always go back to the main list (not the previous detail screen).
I'm struggling with how to accomplish this in SwiftUI?
Here is what I have so far:
struct ListView: View {
#State private var currentDetailShown: Int?
#State private var listItems: [Int] = Array(repeating: 0, count: 10)
func goToNext() {
if let idx = self.currentDetailShown {
self.currentDetailShown = min(self.listItems.count - 1, idx + 1)
}
}
func goToPrev() {
if let idx = self.currentDetailShown {
self.currentDetailShown = max(0, idx - 1)
}
}
var body: some View {
List {
ForEach(0..<listItems.count) { index in
NavigationLink(destination: DetailView(goToNext: self.goToNext, goToPrev: self.goToPrev),
tag: index,
selection: self.$currentDetailShown) {
ListItem(score: listItems[index])
}
.isDetailLink(false)
.onTapGesture {
self.currentDetailShown = index
}
}
}
}
}
What happens with this code is that from the first detail view, it'll move to the to the next detail view and then immediately jump back to the list view.
I feel like I'm overthinking this or missing something obvious...
Instead of navigating to each detail from your list, you can navigate to a detailView that can show each detail individually by using a published variable in an observable object. Here is an example
struct MainView: View{
#EnvironmentObject var viewModel: ViewModel
var body: some View{
NavigationView{
VStack{
ForEach(self.viewModel.details, id:\.self){ detail in
NavigationLink(destination: DetailView(detail: self.viewModel.details.firstIndex(of: detail)!).environmentObject(ViewModel())){
Text(detail)
}
}
}
}
}
}
class ViewModel: ObservableObject{
#Published var showingView = 0
#Published var details = ["detail1", "detail2", "detail3", "detail4", "detail5", "detail6"]
}
struct DetailView: View{
#EnvironmentObject var viewModel: ViewModel
#State var detail: Int
var body: some View{
VStack{
IndivisualDetailView(title: viewModel.details[detail])
Button(action: {
self.viewModel.showingView -= 1
}, label: {
Image(systemName: "chevron.left")
})
Button(action: {
self.viewModel.showingView += 1
print(self.viewModel.showingView)
}, label: {
Image(systemName: "chevron.right")
})
}
}
}
struct IndivisualDetailView: View{
#State var title: String
var body: some View{
Text(title)
}
}