problems in displaying different titles in navigation bar - swift

in my code I have this:
var body: some View {
NavigationView {
VStack(spacing: 0) {
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 18) {
ScrollView(.horizontal, showsIndicators: false){
HStack(spacing: 20){
Text("teste")
.frame(height: 180)
.frame(width: 330)
.background(Color.blue)
.cornerRadius(15)
Text("teste")
.frame(height: 180)
.frame(width: 330)
.background(Color.green)
.cornerRadius(15)
Text("teste")
.frame(height: 180)
.frame(width: 330)
.background(Color.pink)
.cornerRadius(15)
}.padding(.horizontal, 12)
}
ForEach(specialtyList, id: \.type){ specialty in
NavigationLink (destination: SearchBar(item: specialty)){
VStack(spacing: 18) {
HStack{
Text(specialty.type).foregroundColor(.white)
specialty.image
.renderingMode(.original)
.resizable()
.frame(width: 35, height: 35)
}.frame(minWidth: 0, idealWidth: 350, maxWidth: .infinity)
.frame(height: 100)
}
}.padding(.horizontal)
.background(specialty.color).cornerRadius(45)
}.padding(.horizontal)
}
.padding(.top)
.padding(.bottom)
}
}.navigationBarTitle("Buscar")
}.accentColor(.black)
}
I want that, according to the button that is pressed in ForEach, the tittle of the next view is the name of the button. Now, the name displaying in every button is "Buscar"
I've tried to implement, after ForEach and before NavigationLink, a NavigationView, but the hole ForEach disappears. Basically, I want that the Text(specialty.type) that is pressed is the name of the navigation bar back button. Any ideas?

Here is a demo of possible approach - the idea is to change navigation bar title on the navigation link activation and reset on back.
Demo prepared & tested with Xcode 12 / iOS 14
struct DemoView: View {
#State private var barTitle = ""
#State private var selectedType = ""
#State private var isActive = false
let types = ["Type1", "Type2", "Type3"]
var body: some View {
NavigationView {
VStack {
ForEach(types, id: \.self) { type in
Button(type) {
self.isActive = true
self.selectedType = type
self.barTitle = type // comment for variant 2
}
}
}
.background(
NavigationLink(destination:
Text(selectedType)
// .onAppear { self.barTitle = self.selectedType } // variant 2
, isActive: $isActive) { EmptyView() }
)
.navigationBarTitle(barTitle)
.onAppear {
self.barTitle = "Buscar"
}
}
}
}

Related

Why does my custom tab bar get pushed up everytime the on screen keyboard pops up?

I've made a custom tab bar and I'm trying to add a search bar on one of my views. For some reason half of the tab gets pushed up when the onscreen keyboard appears. Ive tried " .ignoresSafeArea(.keyboard)" literally everywhere and the only thing that happens is my icons for the tab disappear but the top of the tab still stays there. I've been trying to fix it for the last 24 hours but I'm getting nowhere, can somebody please help me with this. Thanks!
Code for Custom Tab Bar:
import SwiftUI
enum Tabs: Int {
case home = 0
case hot = 1
case favourites = 2
case settings = 3
}
struct TabBar: View {
#Binding var selectedTab : Tabs
var body: some View {
HStack (alignment: .center){
Button {
//switch to home
selectedTab = .home
} label: {
GeometryReader { geo in
if selectedTab == .home {
Rectangle()
// .ignoresSafeArea(.keyboard)
.foregroundColor(.red)
.frame(width: geo.size.width/2, height: 5)
.padding(.leading, geo.size.width/4)
}
VStack (alignment: .center){
Image(systemName: "house")
// .ignoresSafeArea(.keyboard)
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
.padding(.top, 40.0)
}
// .ignoresSafeArea(.keyboard)
.frame(width: geo.size.width, height: geo.size.height)
}
//.ignoresSafeArea(.keyboard)
.offset(y: -35)
//.ignoresSafeArea(.keyboard)
}
// .ignoresSafeArea(.keyboard)
.tint(Color.black)
Button {
//Switch views
selectedTab = .hot
} label: {
GeometryReader { geo in
if selectedTab == .hot {
Rectangle()
//.ignoresSafeArea(.keyboard)
.foregroundColor(.red)
.frame(width: geo.size.width/2, height: 5)
.padding(.leading, geo.size.width/4)
}
VStack (alignment: .center){
Image(systemName: "flame")
//.ignoresSafeArea(.keyboard)
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
.padding(.top, 40.0)
}
//.ignoresSafeArea(.keyboard)
.frame(width: geo.size.width, height: geo.size.height)
}
//.ignoresSafeArea(.keyboard)
.offset(y: -35)
}
//.ignoresSafeArea(.keyboard)
.tint(Color.black)
Button {
//Switch views
selectedTab = .favourites
} label: {
GeometryReader { geo in
if selectedTab == .favourites {
Rectangle()
//.ignoresSafeArea(.keyboard)
.foregroundColor(.red)
.frame(width: geo.size.width/2, height: 5)
.padding(.leading, geo.size.width/4)
}
VStack (alignment: .center){
Image(systemName: "star")
//.ignoresSafeArea(.keyboard)
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
.padding(.top, 40.0)
}//.ignoresSafeArea(.keyboard)
.frame(width: geo.size.width, height: geo.size.height)
}
//.ignoresSafeArea(.keyboard)
.offset(y: -35)
}
//.ignoresSafeArea(.keyboard)
.tint(Color.black)
Button {
//Switch views
selectedTab = .settings
} label: {
GeometryReader { geo in
if selectedTab == .settings {
Rectangle()
//.ignoresSafeArea(.keyboard)
.foregroundColor(.red)
.frame(width: geo.size.width/2, height: 5)
.padding(.leading, geo.size.width/4)
}
VStack (alignment: .center){
Image(systemName: "gear")
//.ignoresSafeArea(.keyboard)
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
.padding(.top, 40.0)
}//.ignoresSafeArea(.keyboard)
.frame(width: geo.size.width, height: geo.size.height)
}
//.ignoresSafeArea(.keyboard)
.offset(y: -35)
}
//.ignoresSafeArea(.keyboard)
.tint(Color.black)
}
//.ignoresSafeArea(.keyboard, edges: .all)
.frame(height: 20)
}
}
struct TabBar_Previews: PreviewProvider {
static var previews: some View {
TabBar(selectedTab: .constant(.home))
.ignoresSafeArea(.keyboard)
}
}
ignoresSafeArea is commented in all the places I tried to put it.
Here is also the code for the search bar, maybe I need to input ignoreSafeArea somewhere here?
import SwiftUI
struct SearchBarView: View {
#StateObject var im = SearchBarContents()
//#State var selectedTabs: SearchB = .search
#State private var query = ""
var body: some View {
NavigationView {
List {
ForEach(im.filteredData) { item in
HStack{
NavigationLink(destination: ItemView(item: item))
{
SBarView(item: item)
}
}
}
}
.navigationTitle("Items")
.searchable(text: $query,
placement: .navigationBarDrawer(displayMode: .always),
prompt: "Find an Item") {
}
.onSubmit(of: .search) {
im.search(with: query)
}
.onChange(of: query) { newQuery in
im.search(with: newQuery)
}
.onAppear {
im.search()
}
}
}
}
struct SearchBarView_previews: PreviewProvider {
static var previews: some View {
SearchBarView()
}
}
If there is anything missing please let me know.
Imgur link to see exactly what I mean
https://imgur.com/fv79bKh
https://imgur.com/a/Rx9Ki6c (after I add ignoresafearea)
Depends on how you have set up your project. If you have a RootView which is controlling the View to be displayed based on the selection of your Tab Bar and is also the location in the project that you would add the Custom Tab Bar, then on that root View add the modifier .ignoresSafeArea(.keyboard)
For example:
enum Tabs: Int {
case home = 0
case hot = 1
case favourites = 2
case settings = 3
}
struct RootView: View {
#State private var selectedTab = Tabs.home
var body: some View {
VStack {
switch selectedTab {
case .home:
HomeView()
case .hot:
HotView()
case .favourites:
FavouritesView()
case .settings:
SettingsView()
}
Spacer()
CustomTabBar(selectedTab: $selectedTab)
}
.ignoresSafeArea(.keyboard)
}
}

How to do a "reveal"-style collapse/expand animation in SwiftUI?

I'd like to implement an animation in SwiftUI that "reveals" the content of a view to enable expand/collapse functionality. The content of the view I want to collapse and expand is complex: It's not just a simple box, but it's a view hierarchy of dynamic height and content, including images and text.
I've experimented with different options, but it hasn't resulted in the desired effect. Usually what happens is that when I "expand", the whole view was shown right away with 0% opacity, then gradually faded in, with the buttons under the expanded view moving down at the same time. That's what happened when I was using a conditional if statement that actually added and removed the view. So that makes sense.
I then experimented with using a frame modifier: .frame(maxHeight: isExpanded ? .infinity : 0). But that resulted in the contents of the view being "squished" instead of revealed.
I made a paper prototype of what I want:
Any ideas on how to achieve this?
Something like this might work. You can modify the height of what you want to disclose to be 0 when hidden or nil when not so that it'll go for the height defined by the views. Make sure to clip the view afterwards so the contents are not visible outside of the frame's height when not disclosed.
struct ContentView: View {
#State private var isDisclosed = false
var body: some View {
VStack {
Button("Expand") {
withAnimation {
isDisclosed.toggle()
}
}
.buttonStyle(.plain)
VStack {
GroupBox {
Text("Hi")
}
GroupBox {
Text("More details here")
}
}
.frame(height: isDisclosed ? nil : 0, alignment: .top)
.clipped()
HStack {
Text("Cancel")
Spacer()
Text("Book")
}
}
.frame(maxWidth: .infinity)
.background(.thinMaterial)
.padding()
}
}
No, this wasn't trying to match your design, either. This was just to provide a sample way of creating the animation.
Consider the utilization of DisclosureGroup. The following code should be a good approach to your idea.
struct ContentView: View {
var body: some View {
List(0...20, id: \.self) { idx in
DisclosureGroup {
HStack {
Image(systemName: "person.circle.fill")
VStack(alignment: .leading) {
Text("ABC")
Text("Test Test")
}
}
HStack {
Image(systemName: "globe")
VStack(alignment: .leading) {
Text("ABC")
Text("X Y Z")
}
}
HStack {
Image(systemName: "water.waves")
VStack(alignment: .leading) {
Text("Bla Bla")
Text("123")
}
}
HStack{
Button("Cancel", role: .destructive) {}
Spacer()
Button("Book") {}
}
} label: {
HStack {
Spacer()
Text("Expand")
}
}
}
}
The result looks like:
I coded this in under 5 minutes. So of course the design can be optimized to your demands, but the core should be understandable.
import SwiftUI
struct TaskViewCollapsible: View {
#State private var isDisclosed = false
let header: String = "Review Page"
let url: String
let tasks: [String]
var body: some View {
VStack {
HStack {
VStack(spacing: 5) {
Text(header)
.font(.system(size: 22, weight: .semibold))
.foregroundColor(.black)
.padding(.top, 10)
.padding(.horizontal, 20)
.frame(maxWidth: .infinity, alignment: .leading)
Text(url)
.font(.system(size: 12, weight: .regular))
.foregroundColor(.black.opacity(0.4))
.padding(.horizontal, 20)
.frame(maxWidth: .infinity, alignment: .leading)
}
Spacer()
Image(systemName: self.isDisclosed ? "chevron.up" : "chevron.down")
.padding(.trailing)
.padding(.top, 10)
}
.onTapGesture {
withAnimation {
isDisclosed.toggle()
}
}
FetchTasks()
.padding(.horizontal, 20)
.padding(.bottom, 5)
.frame(height: isDisclosed ? nil : 0, alignment: .top)
.clipped()
}
.background(
RoundedRectangle(cornerRadius: 8)
.fill(.black.opacity(0.2))
)
.frame(maxWidth: .infinity)
.padding()
}
#ViewBuilder
func FetchTasks() -> some View {
ScrollView(.vertical, showsIndicators: true) {
VStack {
ForEach(0 ..< tasks.count, id: \.self) { value in
Text(tasks[value])
.font(.system(size: 16, weight: .regular))
.foregroundColor(.black)
.padding(.vertical, 0)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
.frame(maxHeight: CGFloat(tasks.count) * 20)
}
}
struct TaskViewCollapsible_Previews: PreviewProvider {
static var previews: some View {
TaskViewCollapsible(url: "trello.com", tasks: ["Hello", "Hello", "Hello"])
}
}

Using TextField hides the ScrollView beneath it in VStack

This view hold a list of pdf names which when tapped open webviews of pdf links.
The view has a search bar above the list which when tapped causes the scrollview to disappear.
struct AllPdfListView: View {
#Environment(\.presentationMode) var mode: Binding<PresentationMode>
#ObservedObject var pdfsFetcher = PDFsFetcher()
#State var searchString = ""
#State var backButtonHidden: Bool = false
#State private var width: CGFloat?
var body: some View {
GeometryReader { geo in
VStack(alignment: .leading, spacing: 1) {
HStack(alignment: .center) {
Image(systemName: "chevron.left")
Text("All PDFs")
.font(.largeTitle)
Spacer()
}
.padding(.leading)
.frame(width: geo.size.width, height: geo.size.height / 10, alignment: .leading)
.background(Color(uiColor: UIColor.systemGray4))
.onTapGesture {
self.mode.wrappedValue.dismiss()
}
HStack(alignment: .center) {
Image(systemName: "magnifyingglass")
.padding([.leading, .top, .bottom])
TextField ("Search All Documents", text: $searchString)
.textFieldStyle(PlainTextFieldStyle())
.autocapitalization(.none)
Image(systemName: "slider.horizontal.3")
.padding(.trailing)
}
.overlay(RoundedRectangle(cornerRadius: 10).stroke(.black, lineWidth: 1))
.padding([.leading, .top, .bottom])
.frame(width: geo.size.width / 1.05 )
ScrollView {
ForEach($searchString.wrappedValue == "" ? pdfsFetcher.pdfs :
pdfsFetcher.pdfs.filter({ pdf in
pdf.internalName.contains($searchString.wrappedValue.lowercased())
})
, id: \._id) { pdf in
if let parsedString = pdf.file?.split(separator: "-") {
let request = URLRequest(url: URL(string: "https://mylink/\(parsedString[1]).pdf")!)
NavigationLink(destination: WebView(request: request)
.navigationBarBackButtonHidden(backButtonHidden)
.navigationBarHidden(backButtonHidden)
.onTapGesture(perform: {
backButtonHidden.toggle()
})) {
HStack(alignment: .center) {
Image(systemName: "doc")
.padding()
.frame(width: width, alignment: .leading)
.lineLimit(1)
.alignmentGuide(.leading, computeValue: { dimension in
self.width = max(self.width ?? 0, dimension.width)
return dimension[.leading]
})
Text(pdf.internalName)
.padding()
.multilineTextAlignment(.leading)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
}
.padding(.leading)
}
}
}
.navigationBarHidden(true)
}
.accentColor(Color.black)
.onAppear{
pdfsFetcher.pdfs == [] ? pdfsFetcher.fetchPDFs() : nil
}
}
}
}
}
Pdf list and Searchbar.
The same view on Searchbar focus.
I would like the search string to filter the list of pdfs while maintaining the visibility of the list.
I was able to fix this by making my #ObservableObject an #EnvironmentObject in my App :
#main
struct MyApp: App {
#ObservedObject var pdfsFetcher = PDFsFetcher()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(pdfsFetcher)
}
}
}
struct AllPdfListView: View {
#EnvironmentObject var pdfsFetcher: PDFsFetcher
}

Style the picker in SwiftUI

I'm doing a login form in Swiftui, however when i try to add a form that contain picker for age the style get messed up and since im new to SwiftUI i didn't know how can i fix this problem?
How can i fix the picker style so it addapte with the Nickname field?
here it is the code that i tried:
#State var nickName: String = ""
#State var age: Int = 18
var body: some View {
VStack {
HStack {
Image(systemName: "person.circle.fill")
.foregroundColor(Color("ChatiwVeryLightBlue"))
.frame(width: 44, height: 44)
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.shadow(color: Color.black.opacity(0.15), radius: 5, x: 0, y: 5)
.padding(.all, 10)
TextField("Nickname", text: $nickName)
.frame(maxWidth: .infinity)
.frame(height: 50)
}
Divider().padding(.leading, 80)
HStack {
Image(systemName: "person.circle.fill")
.foregroundColor(Color("ChatiwVeryLightBlue"))
.frame(width: 44, height: 44)
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.shadow(color: Color.black.opacity(0.15), radius: 5, x: 0, y: 5)
.padding(.all, 10)
Form {
Section {
Picker(selection: $age, label: Text("Picker")) {
ForEach(18 ..< 99, id: \.self) { index in
Text("\(index)").tag(1)
}
}
}
}
}
// Age
// Country
// Male/Female
}
.background(BlurView(blurType: .systemMaterial))
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.padding()
.offset(y: 400)
Not sure if you are still working on this project, but here is a neat workaround:
struct ContentView: View {
#State private var age = 18
var body: some View {
NavigationView {
VStack {
Divider()
HStack {
NavigationLink(destination: AgePickerView(age: $age)) {
HStack {
Text("Age")
Spacer()
Text("\(age) >")
.padding(.trailing)
}
}
}
.foregroundColor(.gray)
Divider()
}
.padding(.leading, 80)
}
}
struct AgePickerView: View {
#Binding var age: Int
var body: some View {
List(18..<99) { index in
Button(action: {
self.age = index
}) {
HStack {
Text("\(index)")
Spacer()
if index == age {
Image(systemName: "checkmark")
.foregroundColor(.blue)
}
}
}
}
}
}
}
To get the same NavigationView style but remove the Form style, you need to wrap a NavigationLink in a NavigationView.
The little > sign I put at the end of the Text view in the NavigationLink doesn't perfectly match the one in the Form style, so you can look around for an image that is more similar if you would like.
The AgePickerView is pretty straightfoward; it's just a list of buttons that are modifying a binding to the original state variable in ContentView through tap gestures. The checkmark is there to imitate a Picker view.
The best part of this is that you don't have to worry about all that index shifting with a picker.

ScrollView + Custom View + ContextMenu animation glitch - SwiftUI

Is there a workaround to fix these animation glitches? Glitch video
The animation glitch on scroll
The one where the view disappears for less than a second
The view semantics is:
TabView {
NavigationView {
ScrollView {
VStack {
ForEach(){
MyCustomView()
.contextMenu()
MyCustomView is below, if anyone would want to test it:
struct CardVew: View {
var title: String
var description: String
var name: String
var task: String
var done: Bool
let tapVibration = UIImpactFeedbackGenerator(style: .light)
#State var details = false
var body: some View {
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 15)
.foregroundColor(Color("cardGray"))
.opacity(0.24)
VStack(alignment: .leading, spacing: .zero) {
titleBlock
descriptionBlock
bottomBlock
}
.padding([.leading, .trailing], 20)
.padding(.bottom, 15)
.padding(.top, 24)
}
.padding([.leading, .trailing], 12)
.sheet(isPresented: $details) {
CardSheetView(
title: title,
description: description,
task: task,
name: name
)
}
.onTapGesture {
tapVibration.impactOccurred()
details = true
}
.onAppear {
tapVibration.prepare()
}
}
private var titleBlock: some View {
HStack(alignment: .top) {
Text(title)
.font(.system(size: 22, weight: .bold))
.fixedSize(horizontal: false, vertical: true)
.frame(width: 244, alignment: .leading)
.padding(.bottom, 4)
if done {
Spacer()
Image("done")
.opacity(0.8)
}
}
}
private var bottomBlock: some View {
HStack(alignment: .bottom) {
HStack(alignment: .center) {
Image(systemName: "calendar")
Text("22.09")
}
Spacer()
Text(name)
.multilineTextAlignment(.trailing)
.opacity(0.6)
.font(.footnote)
.frame(width: 211, alignment: .trailing)
}
}
private var descriptionBlock: some View {
Text(description)
.opacity(0.8)
.fixedSize(horizontal: false, vertical: true)
.frame(width: 247, alignment: .leading)
.padding(.bottom, 38)
}
}
i tried to exclude all animations, sheet and anything that could cause such behaviour, but had no success