How to hide TabView in NavigationLink? - ios

First of all I did a full research for my problem and NOTHING on Google helped me,
the problem is simple, it seems that the solution should also be simple, but I can't hide the tabbar in NavigationLink, and if something works out, then wierd behavior of the buttons and the transition back, etc...
TabView itself
import SwiftUI
struct Main: View {
#State var currentTab: Int = 0
var body: some View {
TabView(selection: $currentTab) {
HomeView().tag(0)
AccountInfoView().tag(1)
SettingsView().tag(2)
}
.tabViewStyle(.page(indexDisplayMode: .never))
.edgesIgnoringSafeArea(.bottom)
.overlay(
TabBarView(currentTab: $currentTab),
alignment: .top
)
}
}
struct TabBarView: View {
#Binding var currentTab: Int
#Namespace var namespace
var tabBarOptions: [String] = ["Search", "Items", "Account"]
var body: some View {
HStack(spacing: 0) {
ForEach(Array(zip(self.tabBarOptions.indices,
self.tabBarOptions)),
id: \.0,
content: {
index, name in
TabBarItem(currentTab: self.$currentTab,
namespace: namespace.self,
tabBarItemName: name,
tab: index)
})
}
.padding(.top)
.background(Color.clear)
.frame(height: 100)
.edgesIgnoringSafeArea(.all)
}
}
struct TabBarItem: View {
#Binding var currentTab: Int
let namespace: Namespace.ID
var tabBarItemName: String
var tab: Int
var body: some View {
Button {
self.currentTab = tab
} label: {
VStack {
Spacer()
Text(tabBarItemName)
if currentTab == tab {
CustomColor.myColor
.frame(height: 2)
.matchedGeometryEffect(id: "underline",
in: namespace,
properties: .frame)
} else {
Color.clear.frame(height: 2)
}
}
.animation(.spring(), value: self.currentTab)
}
.fontWeight(.heavy)
.buttonStyle(.plain)
}
}
NavigationLink -> this is just the part of the code that contains the NavigationLink, this VStack of course is inside the NavigationView.
struct HomeView: View {
NavigationView {
...
VStack(spacing: 15) {
ForEach(self.data.datas.filter {(self.search.isEmpty ? true : $0.title.localizedCaseInsensitiveContains(self.search))}, id: \.id) { rs in
NavigationLink(
destination: ItemDetails(data: rs)
){
RecentItemsView(data: rs)
}
.buttonStyle(PlainButtonStyle())
}
}
}
}
ItemDetails
struct ItemDetails: View {
let data: DataType
var body : some View {
NavigationView {
VStack {
AsyncImage(url: URL(string: data.pic), content: { image in
image.resizable()
}, placeholder: {
ProgressView()
})
.aspectRatio(contentMode: .fill)
.frame(width: 250, height: 250)
.clipShape(RoundedRectangle(cornerRadius: 12.5))
.padding(10)
VStack(alignment: .leading, spacing: 8, content: {
Text(data.title)
.fontWeight(.bold)
.frame(maxWidth: .infinity, alignment: .center)
Text(data.description)
.font(.caption)
.foregroundColor(.gray)
.frame(maxWidth: .infinity, alignment: .center)
})
.padding(20)
}
.padding(.horizontal)
}
.navigationBarBackButtonHidden(true)
}
}
I apologize for the garbage in the code, it seemed to me that there is not much of it and it does not interfere with understanding the code, also during the analysis of this problem on the Google\SO, I did not need to work with other parts of the code anywhere, except for those that I provided above, but if I missed something, then please let me know, thanks.

Related

SwiftUI Rotation Animation off center

I am trying to make a simple dropdown list item in SwiftUI. This is what the code looks like:
struct SomeObject: Hashable {
var title: String = "title"
var entries: [String] = ["details", "details2", "details3"]
}
struct ContentView: View {
var data: [SomeObject] = [SomeObject()]
var body: some View {
List(data, id: \.self) { item in
HStack {
Text(item.title)
Spacer()
}
ForEach(item.entries, id: \.self) { entry in
ListItemView(entry)
}
}.listStyle(.plain)
}
}
struct ListItemView: View {
#State var expanded: Bool = false
#State var rotation: Double = 0
private let entry: String
init(_ entry: String) {
self.entry = entry
}
var body: some View {
VStack {
Divider().frame(maxWidth: .infinity)
.overlay(.black)
HStack {
Text(entry)
.fixedSize(horizontal: false, vertical: true)
Spacer()
Image(systemName: "chevron.down")
.foregroundColor(.black)
.padding()
.rotationEffect(.degrees(expanded ? 180 : 360))
.animation(.linear(duration: 0.3), value: expanded)
}.padding(.horizontal)
.padding(.vertical, 6)
if expanded {
Text("Details")
}
Divider().frame(maxWidth: .infinity)
.overlay(.black)
}
.listRowSeparator(.hidden)
.listRowInsets(.init())
.onTapGesture {
expanded.toggle()
}
}
}
For some reason when clicking on the list item the animation looks like this:
How can I make the arrow rotate on its center point without moving up or down at all?
The problem you have there is that the arrow is animated but when the hidden text appears, that vertical expansion is not animated. That contrast between an element animated and another that is not makes the chevron looks like it is not doing it properly. So, try to animate the VStack like this:
struct CombineView: View {
#State var expanded: Bool = false
#State var rotation: Double = 0
let entry: String = "Detalle"
var body: some View {
VStack {
Divider().frame(maxWidth: .infinity)
.overlay(.black)
HStack(alignment: .center) {
Text(entry)
.fixedSize(horizontal: false, vertical: true)
Spacer()
Image(systemName: "chevron.down")
.foregroundColor(.black)
.padding()
.rotationEffect(.degrees(expanded ? 180 : 360))
.animation(.linear(duration: 0.3), value: expanded)
}.padding(.horizontal)
.padding(.vertical, 6)
.background(.green)
if expanded {
Text("Details")
}
Divider().frame(maxWidth: .infinity)
.overlay(.black)
}.animation(.linear(duration: 0.3), value: expanded)//Animation added
.listRowSeparator(.hidden)
.listRowInsets(.init())
.onTapGesture {
expanded.toggle()
}
}
}
I hope this works for you ;)

SwiftUI List only taps content

I have a List in SwiftUI that I populate with a custom SwiftUI cell, the issue is that on tap I need to do some stuff and the tap only works when you click the text in the cell, if you click any empty space it will not work. How can I fix this?
struct SelectDraftView: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var viewModel = SelectDraftViewModel()
var body: some View {
VStack {
List {
ForEach(viewModel.drafts.indices, id: \.self) { index in
DraftPostCell(draft: viewModel.drafts[index])
.contentShape(Rectangle())
.onTapGesture {
presentationMode.wrappedValue.dismiss()
}
}
.onDelete { indexSet in
guard let delete = indexSet.map({ viewModel.drafts[$0] }).first else { return }
viewModel.delete(draft: delete)
}
}
.background(Color.white)
Spacer()
}
}
}
struct DraftPostCell: View {
var draft: CDDraftPost
var body: some View {
VStack(alignment: .leading) {
Text(draft.title ?? "")
.frame(alignment: .leading)
.font(Font(UIFont.uStadium.helvetica(ofSize: 14)))
.padding(.bottom, 10)
if let body = draft.body {
Text(body)
.frame(alignment: .leading)
.multilineTextAlignment(.leading)
.frame(maxHeight: 40)
.font(Font(UIFont.uStadium.helvetica(ofSize: 14)))
}
Text(draft.date?.toString(format: "EEEE, MMM d, yyyy") ?? "")
.frame(alignment: .leading)
.font(Font(UIFont.uStadium.helvetica(ofSize: 12)))
}
.padding(.horizontal, 16)
}
}
try adding .frame(idealWidth: .infinity, maxWidth: .infinity) just after DraftPostCell(...). You can also use a minWidth: if required.
EDIT-1: the code I use for testing (on real devices ios 15.6, macCatalyst, not Previews):
import Foundation
import SwiftUI
struct ContentView: View {
var body: some View {
SelectDraftView()
}
}
class SelectDraftViewModel: ObservableObject {
#Published var drafts: [
CDDraftPost] = [
CDDraftPost(title: "item 1", date: Date(), body: "body 1"),
CDDraftPost(title: "item 2", date: Date(), body: "body 4"),
CDDraftPost(title: "item 3", date: Date(), body: "body 3")]
func delete(draft: CDDraftPost) { }
}
struct CDDraftPost: Codable {
var title: String?
var date: Date?
var body: String?
}
struct SelectDraftView: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var viewModel = SelectDraftViewModel()
var body: some View {
VStack {
List {
ForEach(viewModel.drafts.indices, id: \.self) { index in
DraftPostCell(draft: viewModel.drafts[index])
.frame(idealWidth: .infinity, maxWidth: .infinity, alignment: .leading)
.contentShape(Rectangle())
.border(.red) // <-- for testing
.onTapGesture {
print("----> onTapGesture")
// presentationMode.wrappedValue.dismiss()
}
}
.onDelete { indexSet in
guard let delete = indexSet.map({ viewModel.drafts[$0] }).first else { return }
viewModel.delete(draft: delete)
}
}
.background(Color.white)
Spacer()
}
}
}
struct DraftPostCell: View {
var draft: CDDraftPost
var body: some View {
VStack(alignment: .leading) {
Text(draft.title ?? "")
.frame(alignment: .leading)
// .font(Font(UIFont.uStadium.helvetica(ofSize: 14)))
.padding(.bottom, 10)
if let body = draft.body {
Text(body)
.frame(alignment: .leading)
.multilineTextAlignment(.leading)
.frame(maxHeight: 40)
// .font(Font(UIFont.uStadium.helvetica(ofSize: 14)))
}
Text(draft.date?.formatted() ?? "")
.frame(alignment: .leading)
// .font(Font(UIFont.uStadium.helvetica(ofSize: 12)))
}
.padding(.horizontal, 16)
}
}
I'm probably late but this will be useful for anyone checking this in the future.
You need to add .background() modifier to your view before you do .onTapGesture{...}
so in your ForEach code would be modified like this:
ForEach(viewModel.drafts.indices, id: \.self) { index in
DraftPostCell(draft: viewModel.drafts[index])
.contentShape(Rectangle())
.frame(maxWidth: .infinity) // you should use the frame parameter according to your needs, but if you want your cell to occupy the whole width of your scroll view, use this one
.background() // this makes the empty portions of view 'non-transparent', so those portions also receive the tap gesture
.onTapGesture {
presentationMode.wrappedValue.dismiss()
}
}
P.S if you need the whole portion of your scroll view cell to receive the tap gesture you'll also need to add .frame(...) modifier, so it has the exact background you want

SwiftUI unlimited TabView item

I got 50 questions on Firebase. I return this data with ForEach in the TabView I prepared as a custom. But from question 23, I can't see other questions. I can't advance to the next page. What is the reason of this ? Can't I return items in TabView unlimitedly?
Gif:
TabView PagingView:
struct PagingQuestionView: View {
var tests: [Test] = []
#State var imageUrl: String = ""
var storage = Storage.storage().reference()
#ObservedObject var pagingQuestionOptionConfigure: PagingQuestionOptionConfigure = PagingQuestionOptionConfigure()
var body: some View {
GeometryReader { proxy in
TabView(selection: $pagingQuestionOptionConfigure.pageIndex) {
ForEach(tests, id: \.self) { item in
VStack(spacing: 20) {
EmptyView()
.frame(width: 350, height: 250)
.padding()
VStack {
Text("\(item.id))")
Text("\(item.question)")
.padding()
}
PagingOptionView(options: item.sections)
}
.tag(item.id)
}
.rotationEffect(.degrees(-90))
.frame(width: proxy.size.width, height: proxy.size.height)
}
.frame(width: proxy.size.height, height: proxy.size.width)
.rotationEffect(.degrees(90), anchor: .topLeading)
.offset(x: proxy.size.width)
.modifier(TabViewModifier())
}
}
}
TabViewModifier:
struct TabViewModifier: ViewModifier {
#ViewBuilder
func body(content: Content) -> some View {
if #available(iOS 14.0, *) {
content
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
} else {
content
}
}
}
I solved my problem.
I added this in ForEach
ForEach(tests.indices, id: \.self){ ... }

Implementing custom sidebar across all views - SwiftUI

OUTLINE
I have made a custom slimline sidebar that I am now implementing across the whole app. The sidebar consists of a main button that is always showing and when pressed it shows or hides the rest of the sidebar that consists of buttons navigating to other views.
I am currently implementing the sidebar across the app on each view by creating a ZStack like this:
struct MainView: View {
var body: some View {
ZStack(alignment: .topLeading) {
SideBarCustom()
Text("Hello, World!")
}
}
}
PROBLEM
I am planning on adding a GeometryReader so if the side bar is shown the rest of the content moves over. With this in mind, the way I am implementing the sidebar on every view feels clunky and a long winded way to add it. Is there a more simple/better method to add this to each view?
Sidebar Code:
struct SideBarCustom: View {
#State var isToggle = false
var names = ["Home", "Products", "Compare", "AR", "Search"]
var icons = ["house.fill", "printer.fill.and.paper.fill", "list.bullet.rectangle", "arkit", "magnifyingglass"]
var imgSize = 20
var body: some View {
GeometryReader { geo in
VStack {
Button(action: {
self.isToggle.toggle()
}, label: {
Image("hexagons")
.resizable()
.frame(width: 40, height: 40)
.padding(.bottom, 20)
})
if isToggle {
ZStack{
RoundedRectangle(cornerRadius: 5)
.foregroundColor(Color.red)
.frame(width: 70, height: geo.size.height)
VStack(alignment: .center, spacing: 60) {
ForEach(Array(zip(names, icons)), id: \.0) { item in
Button(action: {
// NAVIIGATE TO VIEW
}, label: {
VStack {
Image(systemName: item.1)
.resizable()
.frame(width: CGFloat(imgSize), height: CGFloat(imgSize))
Text(item.0)
}
})
}
}
}
}
}
}
}
}
I don't think there's necessarily a reason to use GeometryReader here. The following is an example that has a dynamic width sidebar (although you could set it to a fixed value) that slides in and out. The main content view resizes itself automatically, since it's in an HStack:
struct ContentView : View {
#State private var sidebarShown = false
var body: some View {
HStack {
if sidebarShown {
CustomSidebar(sidebarShown: $sidebarShown)
.frame(maxHeight: .infinity)
.border(Color.red)
.transition(sidebarShown ? .move(edge: .leading) : .move(edge: .trailing) )
}
ZStack(alignment: .topLeading) {
MainContentView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
if !sidebarShown {
Button(action: {
withAnimation {
sidebarShown.toggle()
}
}) {
Image(systemName: "info.circle")
}
}
}
}
}
}
struct CustomSidebar : View {
#Binding var sidebarShown : Bool
var body: some View {
VStack {
Button(action: {
withAnimation {
sidebarShown.toggle()
}
}) {
Image(systemName: "info.circle")
}
Spacer()
Text("Hi")
Text("There")
Text("World")
Spacer()
}
}
}
struct MainContentView: View {
var body: some View {
VStack {
Text("Main content")
}
}
}

SwiftUI list showing an array is not updated

In my app in SwiftUI, there is a list showing all items in an array. When I click on one item, its details are shown and can be modified. Those changes are stored in the array, but when I go back to the list view, the changes are only reflected after I made a change to that list array, like adding or moving an item. Can I make this list refresh when it re-appears?
My main view looks like this:
import SwiftUI
struct ContentView: View {
#State var items: [Item]
var body: some View {
NavigationView {
List {
ForEach(items) { item in
NavigationLink(destination: ItemDetailView(items: self.$items, index: self.items.firstIndex(of: item)!)) {
HStack {
VStack(alignment: .leading) {
Text(item.name).font(.title)
if item.serialNumber != nil {
Text(item.serialNumber!)
.font(.subheadline)
.foregroundColor(.secondary)
}
}
Spacer()
Text("\(item.valueInDollars)$").font(.title)
}
}
}
.onDelete(perform: delete)
.onMove(perform: move)
Text("No more items!")
}
.navigationBarTitle(Text("Homepwner"), displayMode: .inline)
.navigationBarItems(leading: EditButton(), trailing: Button(action: addItem) { Text("Add") })
}
}
//... functions
}
The detail view looks like this:
import SwiftUI
struct ItemDetailView: View {
#Binding var items: [Item]
let index: Int
var body: some View {
VStack {
VStack(alignment: .leading) {
HStack {
Text("Name: ")
TextField("Item Name", text: $items[index].name)
}
//... more TextFields
}
.padding(.all, 8.0)
VStack(alignment: .center) {
//... button
Image(systemName: "photo")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
}.navigationBarTitle(items[index].name)
}
}
The class Item is Identifiable and Equatable and only holds the necessary members, the class ItemStore only holds an array of Items.
i just tried this (had to enhance code so it was compilable at all) and it works:
import SwiftUI
struct Item : Equatable, Identifiable, Hashable {
var id = UUID()
var name: String
var serialNumber : String?
var valueInDollars : Int = 5
}
struct ContentView: View {
#State var items: [Item] = [Item(name: "hi"), Item(name: "ho")]
var body: some View {
NavigationView {
List {
ForEach(items, id: \.self) { item in
NavigationLink(destination: ItemDetailView(items: self.$items, index: self.items.firstIndex(of: item)!)) {
HStack {
VStack(alignment: .leading) {
Text(item.name).font(.title)
if item.serialNumber != nil {
Text(item.serialNumber!)
.font(.subheadline)
.foregroundColor(.secondary)
}
}
Spacer()
Text("\(item.valueInDollars)$").font(.title)
}
}
}
// .onDelete(perform: delete)
// .onMove(perform: move)
Text("No more items!")
}
.navigationBarTitle(Text("Homepwner"), displayMode: .inline)
// .navigationBarItems(leading: EditButton(), trailing: Button(action: addItem) { Text("Add") })
}
}
//... functions
}
struct ItemDetailView: View {
#Binding var items: [Item]
let index: Int
var body: some View {
VStack {
VStack(alignment: .leading) {
HStack {
Text("Name: ")
TextField("Item Name", text: $items[index].name)
}
//... more TextFields
}
.padding(.all, 8.0)
VStack(alignment: .center) {
//... button
Image(systemName: "photo")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
}.navigationBarTitle(items[index].name)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Resources