SwiftUI .searchable new view - swift

I am creating an app and I am using .searchable for my home view, but once it is clicked, I want it to bring up a new view where results of what is being searched is shown, and once the cancel button is clicked goes back to the home view.
I currently have .searchable(text: $searchText) and it is showing the search bar, and I have tried .overlay{SearchView()} but it is just putting it over the home view. Is there a way to do this.

You need to use the #Environment variable isSearching to determine which view to display, e.g.
struct ContentView: View {
#State private var searchText = ""
var body: some View {
NavigationView {
SearchingView(searchText: $searchText)
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: "Search")
.navigationTitle("Title")
}
}
}
struct SearchingView: View {
#Environment(\.isSearching) private var isSearching
#Binding var searchText: String
var body: some View {
if isSearching {
List("This is displayed when searching".components(separatedBy: " "), id: \.self) { word in
Text(word )
}
.listStyle(.plain)
} else {
Text("Here is the normal view")
}
}
}

Related

SwiftUI keyboard-shortcut without modifier doesn't work in popover

In a View with a textfield I have a popover, activated by a button.
In that popover I want to listen to a keyboardShortcut without modifiers.
These keypresses arrive in the textfild of the parent view instead of the popover.
What can I do, to react on these in the popover?
struct ContentView: View {
#State var showPopOver = false
#State var text = ""
var body: some View {
VStack{
TextField("enter text here", text: $text)
Button("show popover"){ showPopOver = true }
.popover(isPresented: $showPopOver) {PopOverView(text: $text)} }
.keyboardShortcut("p")
.padding()
}
}
struct PopOverView: View {
#Environment(\.dismiss) var dismiss
#Binding var text: String
var body: some View {
VStack{
Text("my Popover")
Button("set Tom"){
text = "Tom"
dismiss()
}
.keyboardShortcut("t",modifiers: [])
//.keyboardShortcut("t") // like this it works
Button("set Frank"){
text = "Frank"
dismiss()
}
.keyboardShortcut("f",modifiers: [])
}
.padding()
}
}
KeyboardShortcuts with a modifier in the popover do work (commented out).

How to update UI of a view from another view [ SwiftUI Question]

I am new to Swift & iOS dev in general.
I am building an app in SwiftUI. Assume there are 2 separate views in different files: MainView() & Results() and TabBar()(ignore the naming, this is just an example)(those 2 views are ON 2 SEPARATE TABS)
My goal is to press on that button FROM ResultsView() and change the textValue property of MainView() to something else & then display the change ON the MainView(). How can I do that? P.S Please ignore alignment & etc. I am new to StackOverflow
struct MainView: View {
#State var textValue: String = "Old Text"
var body: some View {
Text(textValue)
}
}
struct TabBar: View {
var body: some View {
TabView {
MainView()
.tabItem {
Label("Main", systemImage: "circle.dashed.inset.filled")
}
ResultsView()
.tabItem {
Label("Results", systemImage: "person.2.crop.square.stack.fill")
}
}
}
}
struct ResultsView: View {
var body: some View {
Button {
// HOW TO UPDATE MainView.textValue?
} label: {
Text("Update")
}
}
}
ContentView() just has TabBar() in body
Basically, my question is how do we change UI of certain view from another view? Without NavigationLink
Might sound like a rookie question, but any reply will be so appreciated!
You can achieve this by passing the textValue from MainView to DetailView through a #Binding.
struct MainView: View {
#State private var text: String = "Original"
var body: some View {
NavigationView {
VStack {
Text(text)
NavigationLink("Detail", destination: DetailView(text: $text))
}
}
}
}
struct DetailView: View {
#Binding var text: String
var body: some View {
Button("Change Text") {
text = "New Text"
}
}
}
I recommend you read this article about bindings as it explains how to change a view from elsewhere.
Edit:
Because you mentioned, that both views are in a TabBar, here should be a working example:
struct ContentView: View {
#State private var text: String = "Original Text"
var body: some View {
TabView {
MainView(text: $text)
.tabItem { ... }
DetailView(text: $text)
.tabItem { ... }
}
}
}
struct MainView: View {
#Binding var text: String
var body: some View {
Text(text)
}
}
struct DetailView: View {
#Binding var text: String
var body: some View {
Button("Change Text") {
text = "New Text"
}
}
}

SwiftUI NavigationView adding extra view onto stack when sheet is dismissed

I have a List with ForEach using a NavigationLink that when tapped displays a detail view. The DetailsView includes a sheet to save the detail information into an array. After the save, the sheet is dismissed but an additional DetailsView is put on the navigation stack, so that I need to tap the back link twice to get back to the listing.
I'm likely doing something incorrect as I'm relatively new to swiftui, but can't determine what.
Three things of interest:
In the ListView, I use .navigationViewStyle(StackNavigationViewStyle()). When removed, the issue goes away but the iPad gets messy for the ListView.
I'm using insert(at: 0) to add data in my array because I want the most recent data at the top of the listing. If I use append instead, the issue does go away. Wanting the most recently saved item at the top of the list, I add a sort, however sorting causes the duplicate issue to reappear.
The issue only seems to occur when selecting the first item created in the list (the last in the array) and then saving a new item into the array.
steps:
click Tap Here First, then tap SAVE, enter a name then click Save.
click tab bar item Saved.
click on the list item from step 1 in the Saved Items listing (nav bar should show "< Saved Items").
click SAVE, enter another name then click Save. At this point, the duplicate view appears with "< Back" as the leading nav bar item, clicking it takes you to the original detail view, then clicking "< Saved Items" takes you to the list view.
What am I doing wrong or what should I be doing better?
xcode 12.4/iOS 14.1
Stripped down code to reproduce:
struct TestModel: Identifiable, Codable {
private(set) var id: UUID
var name: String
}
class AppData: ObservableObject {
#Published var testList = [TestModel]()
}
struct NewView: View {
var body: some View {
NavigationView {
NavigationLink(
destination: DetailView(item: TestModel(id: UUID(), name: ""))) {
Text("Tap here first")
}.navigationBarTitle("Main View", displayMode: .inline)
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct ListView: View {
#EnvironmentObject var appData: AppData
var body: some View {
NavigationView {
List {
ForEach(appData.testList) { item in
NavigationLink(destination: DetailView(item: item)) {
Text(item.name)
}
}
}
.navigationBarTitle("Saved Items", displayMode: .inline)
}
.navigationViewStyle(StackNavigationViewStyle()) // remove this and issue goes away, but iPad gets "messy".
}
}
struct DetailView: View {
#State private var isSaveShowing = false
#State var item: TestModel
var body: some View {
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .center, spacing: 20) {
Text(item.name)
Button(action: {
isSaveShowing = true
}) {
Text("Save".uppercased())
}.sheet(isPresented: $isSaveShowing) {
SaveView(currentItem: item)
}
}
}
}
}
struct SaveView: View {
var currentItem: TestModel
#State private var name = ""
#EnvironmentObject var appData: AppData
#Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView {
Form {
Section(header: Text("Enter Name ".uppercased())
) {
TextField("Name (required)", text: $name)
}
}
.navigationBarItems(
trailing: Button(action: {
// appData.testList.append(TestModel(id: UUID(), name: name)) // using append instead of insert also resolves issue...
appData.testList.insert(TestModel(id: UUID(), name: name), at: 0)
presentationMode.wrappedValue.dismiss()
}) {
Text("Save")
}
)
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct ContentView: View {
var appData = AppData()
var body: some View {
TabView {
NewView().tabItem {
Image(systemName: "rectangle.stack.badge.plus")
Text("Calculate")
}
ListView().tabItem {
Image(systemName: "tray.and.arrow.down")
Text("Saved")
}
}
.environmentObject(appData)
}
}
Apparently, this must have been a bug in SwiftUI.
Running the same code using Xcode 12.5 beta 3 with iOS 14.5 the issue no longer occurs.

SwiftUI hiding a navigation bar only when looking at ContentView

I have a Content file and am hiding the navigation bar because it takes up space and pushes elements down. One of the buttons in the ContentView redirects (using a navigation link) to another view. In this other view, the navigationBar is still hidden....for simplicity sake, I'll cut out some of the code from ContentView:
//this is the view that looks "fine" (i.e. the navigation bar takes up no space)
struct ContentView: View {
#State private var isPresentedSettings = false
var body: some View {
NavigationView {
ZStack {
VStack {
SettingsButton(isPresentedSettings: $isPresentedSettings)
}
}.navigationBarTitle("").navigationBarHidden(true)
}
}
}
//this is the button that pulls up the settings page view
struct SettingsButton: View {
#Binding var isPresentedSettings: Bool
var body: some View {
NavigationLink (destination: SettingsPageView(isPresentedSettings:
self.$isPresentedSettings)) {
Button(action: { self.isPresentedSettings.toggle() }, label: { Text("Button") })
}
}
}
//This is the view that should have a navigationbar but it doesn't
struct SettingsPageView: View {
#Binding var isPresentedSettings: Bool
var body: some View {
NavigationView {
VStack {
Text("This is a view")
}.navigationBarTitle("Settings", displayMode: .inline)
}
}
}
Also...there may have been typos because I just copied the code over from another computer. Sorry and thank you in advance!
Firstly, you don't need to have this isPresentedSettings variable for presenting a NavigationLink.
NavigationLink(destination: SettingsPageView()) {
Text("Button")
}
And there should be only one NavigationView in your view hierarchy.
This is how your final code can look like:
struct ContentView: View {
#State private var navBarHidden = true
var body: some View {
NavigationView {
ZStack {
VStack {
SettingsButton(navBarHidden: $navBarHidden)
}
}
.navigationBarHidden(navBarHidden)
}
}
}
struct SettingsButton: View {
#Binding var navBarHidden: Bool
var body: some View {
NavigationLink(destination: SettingsPageView(navBarHidden: $navBarHidden)) {
Text("Show View")
}
}
}
struct SettingsPageView: View {
#Binding var navBarHidden: Bool
var body: some View {
VStack {
Text("This is a view")
}
.navigationBarTitle("Settings", displayMode: .inline)
.onAppear {
self.navBarHidden = false
}
.onDisappear {
self.navBarHidden = true
}
}
}

How to trigger sheet on TabView click

How can I show a sheet when I click a tab in TabView? All the examples on the internet use a Button to trigger an update but I want to make the sheet appear when a user clicks one of the tabs in TabView.
I tried changing the boolean state variable in a tabbed view by adding .onAppear(), but it doesn't seem to work.
struct ContentView: View {
#State var showSheet: Bool = false
var body: some View {
return TabView {
HomeView()
.tabItem {
Image(systemName: "house")
}
}
.sheet(isPresented: self.$showSheet) {
SheetView(isShown: self.$showSheet)
}
}
}
In the above example, I basically want SheetView to show up when I click the tab. I don't want to replace HomeView with SheetView since I want it to be a sheet instead of static view. Thanks!
This can be achieved, albeit rather hackishly, by moving your State variables up one level and controlling the flow within a Group. Here, I just moved them to the app state for simplicity.
final class AppState: ObservableObject {
#Published var shouldShowActionSheet: Bool = true
#Published var selectedContentViewTab: ContentViewTabs = .none
}
In SceneDelegate.swift:
. . .
window.rootViewController = UIHostingController(rootView: contentView.environmentObject(AppState()))
. . .
And finally in your view:
public enum ContentViewTabs: Hashable {
case none
case home
}
struct ContentView: View {
#EnvironmentObject var appState: AppState
var body: some View {
Group {
if (appState.selectedContentViewTab == .home && self.appState.shouldShowActionSheet) {
Text("").sheet(isPresented: self.$appState.shouldShowActionSheet, onDismiss: {
self.appState.shouldShowActionSheet.toggle()
self.appState.selectedContentViewTab = .none
}, content: {
Text("Oll Korrect, Chaps!")
})
} else {
TabView(selection: self.$appState.selectedContentViewTab) {
Text("First View")
.font(.title)
.tabItem {
VStack {
Image("first")
Text("First")
}
}.tag(ContentViewTabs.home)
}
}
}
}
}