I got a re-usable searchbar in a separate view that looks like this:
struct SearchBar: View {
#Binding var searchText: String
#Binding var isSearching: Bool
var body: some View {
HStack {
HStack {
TextField("Search terms here", text: $searchText)
}
.onTapGesture(perform: {
isSearching = true
})
.overlay(
HStack {
Image(systemName: "magnifyingglass")
if isSearching {
Button(action: { searchText = "" }, label: {
Image(systemName: "xmark.circle.fill")
})
}
}
)
if isSearching {
Button(action: {
isSearching = false
searchText = ""
}, label: {
Text("Cancel")
})
}
}
}
}
And I'm using the SearchBar in multiple views, like this:
SearchBar(searchText: $textFieldSearch, isSearching: $isSearching)
Is there a way to override/append the functionality of the cancel button:
Button(action: {
isSearching = false
searchText = ""
// pass more functionality here dynamically
},
label: {
Text("Cancel")
})
In some Views, I need to do some additional stuff besides clearing the searchText field and setting isSearching to false.
You can use closure.
Here I created one cancel button closure action and set it as optional.
struct SearchBar: View {
#Binding var searchText: String
#Binding var isSearching: Bool
var cancel: (() -> Void)? // <== Here
var body: some View {
HStack {
HStack {
TextField("Search terms here", text: $searchText)
}
.onTapGesture(perform: {
isSearching = true
})
.overlay(
HStack {
Image(systemName: "magnifyingglass")
if isSearching {
Button(action: { searchText = "" }, label: {
Image(systemName: "xmark.circle.fill")
})
}
}
)
if isSearching {
Button(action: {
isSearching = false
searchText = ""
cancel?() // <== Here
}, label: {
Text("Cancel")
})
}
}
}
}
Usage
SearchBar(searchText: $textFieldSearch, isSearching: $isSearching)
SearchBar(searchText: $textFieldSearch, isSearching: $isSearching) {
// Cancel Action
}
If you need addtional action then you can inject onCancel side effect like
struct SearchBar: View {
#Binding var searchText: String
#Binding var isSearching: Bool
var onCancel: () -> Void = {} // << default does nothing
...
if isSearching {
Button(action: {
isSearching = false
searchText = ""
onCancel() // << here !!
}, label: {
Text("Cancel")
})
}
and use either in default way as you did or providing side effect, like
SearchBar(searchText: $textFieldSearch, isSearching: $isSearching) {
// side effect is here
}
Related
I have list items in SwiftUI, and when I delete list items I want to delete after alert menu, like
"do want to delete your list items, ""yes" or "no"
is it possible?
struct MyView: View {
#State private var selectedUsers: MyModel?
var body: some View {
ScrollView(.vertical, showsIndicators: false, content: {
VStack(content: {
ForEach(datas){ data in
MyRowView(data: data)
.contextMenu {
Button(action: {
self.delete(item: data)
}) {
Text("delete")
}
}
.onTapGesture {
selectedUsers = data
}
} .onDelete { (indexSet) in
self.datas.remove(atOffsets: indexSet)
}})
})}
private func delete(item data: MyModel) {
if let index = datas.firstIndex(where: { $0.id == data.id }) {
datas.remove(at: index)
}
}}
In the delete action you set a #State bool to true, this triggers e.g. a ConfirmationDialog – and only after confirming there, you really delete:
} .onDelete { (indexSet) in
confirmDelete = true // a #State var Bool
}})
.confirmationDialog("Do you really want to delete?", isPresented: $confirmDelete) {
Button("Delete", role: .destructive) {
selectedUsers.remove(atOffsets: indexSet)
}
Button("Cancel", role: .cancel) { }
}
I have a database with several objects with booleans as attribute. I'm looking for a function to invert all boolean objects when I press a button. I tried this function but several errors are displayed like (Value of type 'Bool' has no member 'indices') :
struct ViewList: View {
#Environment(\.managedObjectContext) var context
#State var newName: String = ""
#FetchRequest(
entity: Product.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Product.name, ascending: true)]
) var list: FetchedResults<Product>
var body: some View {
VStack {
HStack {
TextField("I insert the name of the product", text: $newName)
Button(action: { self.add()
self.newName = ""
})
{ Image(systemName: "plus") }
}
List {
ForEach(list, id: \.self) {
product in ViewItem(product: product)
}
}
}
}
public func add() {
let newProduct = Product(context: context)
newProduct.name = newName
do {
try context.save()
} catch {
print(error)
}
}
}
struct ViewItem: View {
#State var product: Product
#State var refresh: Bool = false
var body: some View {
NavigationLink(destination: ViewDetail(product: product, refresh: $refresh)) {
HStack(alignment: .top) {
Button( action: {
self.clean()
self.product.isSelected.toggle()
}) {
if self.product.isSelected == true {
Image(systemName: "checkmark")
} else {
Image(systemName: "checkmark").colorInvert()
}
}
VStack() {
Text(product.name)
if product.password != "" {
Text("Password : " + product.password)
}
Text(String(refresh)).hidden()
}
}
}
.onAppear {
self.refresh = false
}
}
}
I've been thinking about it, but I don't know how to go about it...
func clean() {
for( index ) in self.product.isSelected.indices {
self.product[index]isSelected = false
}
}
You need to create a query to flip the state of the isSelected flag. This logic is best kept out of the view system so you can use it anywhere.
You create a SelectionHandler
import CoreData
class SelectionHandler {
func clearSelection(in context: NSManagedObjectContext) {
for item in currentSelected(in: context) {
item.isSelected = false
}
}
func selectProduct(_ product: Product) {
guard let context = product.managedObjectContext else {
assertionFailure("broken !")
return
}
clearSelection(in: context)
product.isSelected = true
}
func currentSelected(in context: NSManagedObjectContext) -> [Product] {
let request = NSFetchRequest<Product>(entityName: Product.entity().name!)
let predicate = NSPredicate(format: "isSelected == YES")
request.predicate = predicate
do {
let result = try context.fetch(request)
return result
} catch {
print("fetch error =",error)
return []
}
}
}
which you can then use to select your desired product.
SelectionHandler().selectProduct(product)
As it stands your NavigationLink will do nothing because the parent list is not held in a NavigationView so you'll need to change the body of ViewList to look like this.
var body: some View {
NavigationView {
VStack {
HStack {
TextField("Create product with name", text: $newName)
Button(action: {
self.add()
self.newName = ""
})
{ Image(systemName: "plus") }
}
.padding()
List {
ForEach(list, id: \.self) { product in
ViewItem(product: product)
}
}
}
}
}
and in ViewItem , Product should be an ObservedObject so that changes are detected in the managedObject.
struct ViewItem: View {
#ObservedObject var product: Product
#State var refresh: Bool = false
var checkmarkImage: some View {
return Group {
if self.product.isSelected {
Image(systemName: "checkmark")
} else {
Image(systemName: "checkmark").colorInvert()
}
}
}
var body: some View {
NavigationLink(destination: ViewDetail(product: product, refresh: $refresh)) {
HStack {
checkmarkImage
Text(product.name ?? "wat")
}
}
}
}
The original Button won't play with the NavigationLink but you can simply apply the selection to onAppear in ViewDetail
struct ViewDetail: View {
#ObservedObject var product: Product
#Binding var refresh: Bool
var body: some View {
VStack {
Text("Hello, World!")
Text("Product is \(product.name ?? "wat")")
}
.onAppear {
SelectionHandler().selectProduct(self.product)
}
}
}
I want to run another process continuously after dismiss the modal.
In the following code, the modal is not dismissed and only the subsequent processing is performed.
How can I do that?
I used NotificationCenter and callbacks, all with the same result.
struct HomeView: View {
#State private var modalPresented: Bool = false
var body: some View {
VStack {
Button(action: {}) {
Text("setting")
}.sheet(isPresented: self.$modalPresented) {
SettingView(onDismiss: {
self.modalPresented = false
})
}
}
}
}
struct SettingView: View {
var onDismiss: () -> ()
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
// The following is the logout function.
logout()
}) {
Text("logout")
}
}
}
}
do you mean like so?
struct ContentView: View {
#State private var modalPresented: Bool = false
var body: some View {
VStack {
Button(action: {
self.modalPresented.toggle()
}) {
Text("setting")
}
.sheet(isPresented: self.$modalPresented) {
SettingView()
// .onDismiss: {
// self.modalPresented = false
// }
}
}
}
}
struct SettingView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
print("logout")
}) {
Text("logout")
}
}
}
}
I tap on picker it will open screen with list of element on that screen, can we add Search Bar?
I implemented Country Picker, in the country list I am showing country name and country code so on that list screen add Search bar find out country easily.
struct ContentView: View {
//All Country get from the plist with country Code and Coutnry Name.
let countyList = Country().getAllCountyInfo()
// Selected Country
#State private var selectedCountry = 0
var body: some View {
NavigationView {
Form {
Picker(selection: $selectedCountry, label: Text("Country")) {
ForEach(countyList) { country in
HStack{
Text(country.countryName ?? "")
Spacer()
Text(country.countryCode ?? "")
}
}
}
}
.navigationBarTitle("Select country picker")
}
}
}
by run above code it will open a country list like above screen.
on the above screen (country list).
How can I add search bar to filter country data?
yes you can!
SearchBar code:
struct SearchBar: View {
#Binding var text: String
#State var isEditing: Bool = false
#ObservedObject var vc = VC()
var body: some View {
HStack {
TextField("Search Countries", text: $text)
.padding(7)
.padding(.horizontal, 25)
.background(Vc.color2)
.cornerRadius(8)
.overlay(
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.gray)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 8)
if isEditing {
Button(action: {
self.text = ""
}) {
Image(systemName: "multiply.circle.fill")
.foregroundColor(.gray)
.padding(.trailing, 8)
}
}
}
)
.padding(.horizontal, 10)
.onTapGesture {
self.isEditing = true
}
if isEditing {
Button(action: {
UIApplication.shared.endEditing(true)
self.isEditing = false
self.text = ""
}) {
Text("Cancel")
}
.padding(.trailing, 10)
.transition(.move(edge: .trailing))
.animation(.default)
}
}
}
}
extension UIApplication {
func endEditing(_ force: Bool) {
self.windows
.filter{$0.isKeyWindow}
.first?
.endEditing(force)
}
}
struct ResignKeyboardOnDragGesture: ViewModifier {
var gesture = DragGesture().onChanged{_ in
UIApplication.shared.endEditing(true)
}
func body(content: Content) -> some View {
content.gesture(gesture)
}
}
extension View {
func resignKeyboardOnDragGesture() -> some View {
return modifier(ResignKeyboardOnDragGesture())
}
}
Picker with Search Bar:
struct locc: View {
#State var locationsSelection: [String] = //Your Array
#State var searchText = ""
#State var locationSelection: Int = 0
var body: some View {
Picker(selection: $locationSelection, label: Text("Country")) {
SearchBar(text: $searchText)
if self.searchText != "" {
ForEach(0..<locationsSelection.filter{$0.hasPrefix(searchText)}.count) {
Text(locationsSelection.filter{$0.hasPrefix(searchText)}[$0]).tag($0)
}.resignKeyboardOnDragGesture()
}else if self.searchText == "" {
ForEach(0..<locationsSelection.count) {
Text(locationsSelection[$0]).tag($0)
}.resignKeyboardOnDragGesture()
}
}
}
}
Note: I am using the locc struct in another view containing the Form.
I solve this in my blog post here: https://roddy.io/2020/09/07/add-search-bar-to-swiftui-picker/
Create the UIViewRepresentable:
struct SearchBar: UIViewRepresentable {
#Binding var text: String
var placeholder: String
func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
let searchBar = UISearchBar(frame: .zero)
searchBar.delegate = context.coordinator
searchBar.placeholder = placeholder
searchBar.autocapitalizationType = .none
searchBar.searchBarStyle = .minimal
return searchBar
}
func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) {
uiView.text = text
}
func makeCoordinator() -> SearchBar.Coordinator {
return Coordinator(text: $text)
}
class Coordinator: NSObject, UISearchBarDelegate {
#Binding var text: String
init(text: Binding<String>) {
_text = text
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
text = searchText
}
}
}
And then it's easy to embed it in your Picker:
struct FormView: View {
let countries = ["Brazil", "Canada", "Egypt", "France", "Germany", "United Kingdom"]
#State private var pickerSelection: String = ""
#State private var searchTerm: String = ""
var filteredCountries: [String] {
countries.filter {
searchTerm.isEmpty ? true : $0.lowercased().contains(searchTerm.lowercased())
}
}
var body: some View {
NavigationView {
Form {
Picker(selection: $pickerSelection, label: Text("")) {
SearchBar(text: $searchTerm, placeholder: "Search Countries")
ForEach(filteredCountries, id: \.self) { country in
Text(country).tag(country)
}
}
}
}
}
}
I understand there is PresentationButton and NavigationButton in order to change views in the latest SwiftUI. However I want to do a simple operation like below. When user clicks on SignIn button if credentials are correct it will sign them in but also do a segue (in this case change the view). However I could not check if they are correct in PresentationButton and I could not change the view in a normal button.
Is there another way to do that?
#IBAction func signInClicked(_ sender: Any) {
if emailText.text != "" && passwordText.text != "" {
Auth.auth().signIn(withEmail: emailText.text!, password: passwordText.text!) { (userdata, error) in
if error != nil {
//error
} else {
performSegue(withIdentifier: "toFeedActivity", sender: nil)
}
}
} else {
//error
}
}
Here's one way.
struct AppContentView: View {
#State var signInSuccess = false
var body: some View {
return Group {
if signInSuccess {
AppHome()
}
else {
LoginFormView(signInSuccess: $signInSuccess)
}
}
}
}
struct LoginFormView : View {
#State private var userName: String = ""
#State private var password: String = ""
#State private var showError = false
#Binding var signInSuccess: Bool
var body: some View {
VStack {
HStack {
Text("User name")
TextField("type here", text: $userName)
}.padding()
HStack {
Text(" Password")
TextField("type here", text: $password)
.textContentType(.password)
}.padding()
Button(action: {
// Your auth logic
if(self.userName == self.password) {
self.signInSuccess = true
}
else {
self.showError = true
}
}) {
Text("Sign in")
}
if showError {
Text("Incorrect username/password").foregroundColor(Color.red)
}
}
}
}
struct AppHome: View {
var body: some View {
VStack {
Text("Hello freaky world!")
Text("You are signed in.")
}
}
}
I had the same need in one of my app and I've found a solution...
Basically you need to insert your main view in a NavigationView, then add an invisible NavigationLink in you view, create a #state var that controls when you want to push the view and change it's value on your login callback...
That's the code:
struct ContentView: View {
#State var showView = false
var body: some View {
NavigationView {
VStack {
Button(action: {
print("*** Login in progress... ***")
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.showView = true
}
}) {
Text("Push me and go on")
}
//MARK: - NAVIGATION LINKS
NavigationLink(destination: PushedView(), isActive: $showView) {
EmptyView()
}
}
}
}
}
struct PushedView: View {
var body: some View {
Text("This is your pushed view...")
.font(.largeTitle)
.fontWeight(.heavy)
}
}
Try with state & .sheet
struct ContentView: View {
#State var showingDetail = false
var body: some View {
Button(action: {
self.showingDetail.toggle()
}) {
Text("Show Detail")
}.sheet(isPresented: $showingDetail) {
DetailView()
}
}
}
You can use navigation link with tags so,
Here is the code:
first of all, declare tag var
#State var tag : Int? = nil
then create your button view:
Button("Log In", action: {
Auth.auth().signIn(withEmail: self.email, password: self.password, completion: { (user, error) in
if error == nil {
self.tag = 1
print("success")
}else{
print(error!.localizedDescription)
}
})
So when log in success tag will become 1 and when tag will become 1 your navigation link will get executed
Navigation Link code:
NavigationLink(destination: HomeView(), tag: 1, selection: $tag) {
EmptyView()
}.disabled(true)
if you are using Form use .disabled because here the empty view will be visible on form and you don't want your user to click on it and go to the homeView.