How to create a UIBarButtonItem in UIViewControllerRepresentable Struct? - ios

I am trying to add a button in my custom navigation bar, Problem is when I am adding bar button item to the navigation bar it doesn't allow me to create an #objc method, to avoid it I created a different class for objc methods but that doesn't seem to work either, code accepts the selector method but doesn't trigger when tapped during runtime.
Plan is to launch a Side menu on the Tap of this button, The button is added and visible, but the action doesn't work, any work around or help will be greatly appreciated.
struct CustomNavigationBar: UIViewControllerRepresentable {
func makeCoordinator() -> Coordinator {
return CustomNavigationBar.Coordinator(parent: self)
}
// Properties
/// Ease of Use
var view : AnyView
var title : String //Will be location of the user
/// On Search and On Cancel Closures
var onSearch : (String) -> ()
var onCancel : () -> ()
var didTapMenu : () -> ()
// Require Closures on initialization
init(view: AnyView, title : String, onSearch : #escaping (String)->(), onCancel : #escaping () -> (), didTapMenu : #escaping () -> ()) {
self.view = view
self.title = title
self.onSearch = onSearch
self.onCancel = onCancel
self.didTapMenu = didTapMenu
}
// Integrating UIKit Navigation Controller with SwiftUI View
func makeUIViewController(context: Context) -> UINavigationController {
//Requires SwiftUIView
let childView = UIHostingController(rootView: view)
let controller = UINavigationController(rootViewController: childView)
// Navigation Bar Data
controller.navigationBar.topItem?.title = title
controller.navigationBar.prefersLargeTitles = false
// Navigation Bar Customization
controller.navigationBar.barTintColor = UIColor(Color.grubSoulRed)
controller.navigationBar.tintColor = UIColor.white
controller.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.white, .font: UIFont(name: "Poppins-Bold", size: 17)!]
//Adding Navigation Button // ===> Problem Here
let menuButton = UIBarButtonItem(image: UIImage(named: "hamburgerMenu"), style: .plain, target: nil, action: #selector(navigationActions.hamburgerTapped(_:)))
childView.navigationItem.leftBarButtonItem = menuButton
// Search Bar
let searchController = UISearchController()
searchController.searchBar.placeholder = "Search here for products..."
/// Search Bar Customization
searchController.searchBar.searchTextField.backgroundColor = UIColor.white
searchController.searchBar.searchTextField.layer.masksToBounds = true
searchController.searchBar.searchTextField.layer.cornerRadius = 17
searchController.obscuresBackgroundDuringPresentation = true
/// Setting search bar delegate
searchController.searchBar.delegate = context.coordinator
/// Adding Search Bar
controller.navigationBar.topItem?.searchController = searchController
controller.navigationBar.topItem?.hidesSearchBarWhenScrolling = false
return controller
}
func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {
}
// Search Bar delegate
class Coordinator: NSObject, UISearchBarDelegate {
var parent : CustomNavigationBar
init(parent: CustomNavigationBar) {
self.parent = parent
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
self.parent.onSearch(searchText)
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
self.parent.onCancel()
}
}}
open class navigationActions {
#objc func hamburgerTapped(_ sender : UIButton) {
print("hamburger tapped")
// Launch Hamburger menu
}}

No need to create a new class,
Declare hamburgerTapped function inside the Coordinator class and set the target as Coordinator class and access from the Coordinator
let menuButton = UIBarButtonItem(image: UIImage(named: "hamburgerMenu"), style: .plain, target: context.coordinator, action: #selector(context.coordinator.hamburgerTapped(_:))) // <--- Here!!
and inside the Coordinator class
// Search Bar delegate
class Coordinator: NSObject, UISearchBarDelegate {
// Other code
#objc func hamburgerTapped(_ sender : UIButton) { // <-- Here!!
print("hamburger tapped")
// Launch Hamburger menu
}
}

Related

Navigation bar title truncated after relaunching application

Recently I getting e wired problem
When I run my application from XCode navigation title text is showing perfect.
But when I close the application and reluanch again text cuts off with
...
I tried the following questions but no luck.
Here is my BaseViewController
import UIKit
import SnapKit
open class BaseViewController: UIViewController {
public lazy var navigationShadowView: UIView = {
let view = UIView()
DispatchQueue.main.async {
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [UIColor.Blue10.cgColor, UIColor.Blue0.withAlphaComponent(0.0).cgColor]
gradientLayer.frame = view.bounds
view.layer.addSublayer(gradientLayer)
}
return view
}()
public override func viewDidLoad() {
loadDefaults()
setupUI()
}
}
extension BaseViewController {
private func loadDefaults() {
view.backgroundColor = .white
tabBarController?.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
// MARK: Navigation bar bottom shadow
view.addSubview(navigationShadowView)
navigationShadowView.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide.snp.top)
make.leading.equalTo(view.snp.leading)
make.trailing.equalTo(view.snp.trailing)
make.height.equalTo(10)
}
}
#objc open func setupUI() {
}
}
I populate the view in viewcontroller like below
import UIKit
import CoreModule
import SnapKit
import Combine
class CustomerProjectListVC: BaseViewController {
lazy var refreshControl: UIRefreshControl = {
let refresh = UIRefreshControl()
refresh.addTarget(self, action: #selector(refreshProjects(_:)), for: .valueChanged)
return refresh
}()
lazy var jobsTableView: UITableView = {
let tableView = UITableView()
tableView.showsHorizontalScrollIndicator = false
tableView.showsVerticalScrollIndicator = false
tableView.separatorStyle = .none
tableView.rowHeight = 220
tableView.backgroundColor = .Blue0
tableView.addSubview(refreshControl)
tableView.register(ProjectListTableViewCell.self, forCellReuseIdentifier: ProjectListTableViewCell.identifier)
tableView.dataSource = self
tableView.delegate = self
return tableView
}()
private let viewModel = CustomerProjectListViewModel()
private var subscription = Set<AnyCancellable>()
override func viewDidAppear(_ animated: Bool) {
tabBarController?.navigationItem.title = "Project List"
tabBarController?.navigationItem.rightBarButtonItem = nil
}
override func setupUI() {
view.addSubview(jobsTableView)
jobsTableView.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide.snp.top)
make.leading.equalToSuperview()
make.trailing.equalToSuperview()
make.bottom.equalToSuperview()
}
populateData()
}
}
Here is the CustomeTabBarController
class CustomerTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
tabBar.backgroundColor = .white
viewControllers = [
createNavController(for: sideMenuController(), title: "Home", image: UIImage(named: .TabBarHome)!),
createNavController(for: ProfileVC(), title: "Profile", image: UIImage(named: .TabBarProfile)!),
createNavController(for: NewPostVC(), title: "Post", image: UIImage(named: .TabBarPost)!),
createNavController(for: CustomerProjectListVC(), title: "Chatbox", image: UIImage(named: .TabBarChatBox)!),
createNavController(for: HomeVC(), title: "Notification", image: UIImage(named: .TabBarNotification)!)
]
}
}
extension CustomerTabBarController {
fileprivate func createNavController(for rootViewController: UIViewController,
title: String,
image: UIImage) -> UIViewController {
rootViewController.tabBarItem.title = title
rootViewController.tabBarItem.image = image
return rootViewController
}
}
extension CustomerTabBarController {
private func configureSideMenu() {
SideMenuController.preferences.basic.menuWidth = UIScreen.main.bounds.width - 80
SideMenuController.preferences.basic.position = .above
SideMenuController.preferences.basic.direction = .right
}
private func sideMenuController() -> SideMenuController {
configureSideMenu()
return SideMenuController(contentViewController: HomeVC(), menuViewController: SideMenuVC())
}
}
And I am initiating the viewcontroller like following
let viewController = CustomerTabBarController()
let navigationViewController = UINavigationController(rootViewController: viewController)
window?.rootViewController = navigationViewController
window?.makeKeyAndVisible()
Navigation Bar Shrinking after relaunching app
Navigation Bar Title truncated
Here I attached some screenshots.
Following one is launching from XCode
https://pasteboard.co/HHl1vFJYbzeX.png
2nd one is after relaunch
https://pasteboard.co/kw3zRZqic9q7.png
My question is why this does not happen when runs from XCode but when the app relaunchs.
I have tried many ways like setupui from viewdidappear methods and others. But no luck.
Please help me out.
It seems like you're setting the title of a wrong view controller and it's also worth checking if you have the correct navigation hierarchy. My best be would that this is causing the problems.
The typical hierarchy of tab-based apps is the following: UITabBarController (root) → UINavigationController (one per each tab) → YourViewController.
Now, I see that you're setting the navigation title as follows:
tabBarController?.navigationItem.title = "Project List"
This is strange and unusual. The view controller is supposed to set its own title like this:
navigationItem.title = "Project List"
Then its parent UINavigationController will be able use this title.
It's also worth setting the title in viewDidLoad, not it viewDidAppear so that the title is visible before the transition animation and not after it.
So check the hierarchy of the view controllers in the app and make sure each view controller only sets its own navigation title.
If that doesn't help, I'll be happy to retract my answer to avoid confusion.

How can I reuse part of code (recognizer, toolbar) applied to a textview?

I have a class called ThemeVC which has a textview (connected with an IBoutlet) and functionalities applied to it (it has a recognizer that detects the tapped words).
My goal here is that I would like to extract that piece of functionality, and put it maybe in its own class or create a delegate so I could reuse that functionality on other textviews.
Anyone knows how?
I pasted my code below.
(HERE comments, are functions that should be called from any view controller)
import UIKit
class ThemeVC: UIViewController, UITextViewDelegate, UINavigationControllerDelegate {
#IBOutlet weak var themeTextView: UITextView!
var tB = UIBarButtonItem()
// Move away from ThemeVC ... ->
var selectionDict = [String:Int]()
var viewTagCount = Int()
var tap = UIGestureRecognizer()
var firstTimeGrouped = false
// -> ... Move away from ThemeVC
override func viewDidLoad() {
super.viewDidLoad()
themeTextView.delegate = self
loadbuttons ()
//HERE
addTagSelectorToolBar ()
}
func loadbuttons () {
tB = UIBarButtonItem(image: UIImage(systemName: "hand.point.up.left"), style: .plain, target: self, action: #selector(getTag(sender:)))
navigationItem.rightBarButtonItems = [tB]
}
#objc func getTag(sender: AnyObject) {
themeTextView.resignFirstResponder()
//HERE
startTagSelection()
}
}
// Move away from ThemeVC ... ->
extension ThemeVC {
func startTagSelection () {
navigationController?.setToolbarHidden(false, animated: false)
tap.isEnabled = true
tB.isEnabled = false
themeTextView.isEditable = false
themeTextView.isSelectable = false
}
}
extension ThemeVC {
#objc func doneTagSelection(){
navigationController?.setToolbarHidden(true, animated: false)
tap.isEnabled = false
tB.isEnabled = true
themeTextView.isEditable = true
themeTextView.isSelectable = true
firstTimeGrouped = false
}
}
extension ThemeVC {
func addTagSelectorToolBar (){
addTappedTagRecognizer()
tap.isEnabled = false
let done = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneTagSelection))
let spacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
toolbarItems = [spacer, done]
}
}
extension ThemeVC {
func addTappedTagRecognizer () {
tap = UITapGestureRecognizer(target: self, action: #selector(tapResponse(recognizer:)))
tap.delegate = self as? UIGestureRecognizerDelegate
themeTextView.addGestureRecognizer(tap)
}
#objc private func tapResponse(recognizer: UITapGestureRecognizer) {
let location: CGPoint = recognizer.location(in: themeTextView)
let position: CGPoint = CGPoint(x:location.x, y:location.y)
let tapPosition: UITextPosition? = themeTextView.closestPosition(to:position)
if tapPosition != nil {
let textRange: UITextRange? = themeTextView.tokenizer.rangeEnclosingPosition(tapPosition!, with: UITextGranularity.word, inDirection: UITextDirection(rawValue: 1))
if textRange != nil
{
let tappedWord: String? = themeTextView.text(in:textRange!)
print(tappedWord ?? "Unable to get word")
}
}
}
}
// ... -> Move away from ThemeVC
How to test my code:
Create a new project with a storyboard
On the left hand side rename viewcontroller with themeVC, and replace
its code with the code I gave.
On the storyboard, embed the controller in a navigation controller, on right side, change in identity inspector class from view controller to themeVC
add a textview and link it to the IBoutlet
Looking at the parts you want to move away from ThemeVC, I would have to say not everything should be moved away from ThemeVC.
For example, you marked startTagSelection as something you want to move away, but you reference the navigationController which belongs to the view controller so it should ideally not be the responsibility of your UITextView to update your UINavigationBar.
So the two ideas discussed in the comments was using SubClasses and Protocols.
Protocols was the suggestion of Ptit Xav so I will show one way that could be used, Ptit Xav could add an answer if something else was in mind.
I start with creating a protocol
// Name the protocol as you see appropriate
// I add #objc so it can be accessible from Storyboard
// This will be used to `hand over` responsibility of
// a certain action / event
#objc
protocol CustomTextViewTagDelegate: class {
func customTextViewDidStartSelection(_ textView: CustomTextView)
func customTextViewDidFinishSelection(_ textView: CustomTextView)
}
Next I subclass a UITextView to add my own customization
#IBDesignable
class CustomTextView: UITextView {
var selectionDict = [String:Int]()
var viewTagCount = Int()
var tap = UIGestureRecognizer()
var firstTimeGrouped = false
// Name it as you wish
// #IBInspectable added for storyboard accessibility
// You could also make it an IBOutlet if your prefer
// that interaction
#IBInspectable
weak var tagDelegate: CustomTextViewTagDelegate?
func startTagSelection () {
// Remove the commented lines as this should the responsibility of
// the view controller, manage in the view controller using the delegate
// navigationController?.setToolbarHidden(false, animated: false)
// tB.isEnabled = false
tap.isEnabled = true
isEditable = false
isSelectable = false
// Hand over responsibility of this action back whatever
// has subscribed as the delegate to implement anything else
// for this action
tagDelegate?.customTextViewDidStartSelection(self)
}
func addTappedTagRecognizer () {
tap = UITapGestureRecognizer(target: self,
action: #selector(tapResponse(recognizer:)))
tap.delegate = self as? UIGestureRecognizerDelegate
addGestureRecognizer(tap)
}
#objc private func tapResponse(recognizer: UITapGestureRecognizer) {
let location: CGPoint = recognizer.location(in: self)
let position: CGPoint = CGPoint(x:location.x,
y: location.y)
let tapPosition: UITextPosition? = closestPosition(to:position)
if tapPosition != nil {
let textRange: UITextRange? = tokenizer.rangeEnclosingPosition(tapPosition!,
with: UITextGranularity.word,
inDirection: UITextDirection(rawValue: 1))
if textRange != nil
{
let tappedWord: String? = text(in:textRange!)
print(tappedWord ?? "Unable to get word")
}
}
}
#objc func doneTagSelection() {
// This is not the text view's responsibility, manage in the
// view controller using the delegate
// navigationController?.setToolbarHidden(true, animated: false)
// tB.isEnabled = true
tap.isEnabled = false
isEditable = true
isSelectable = true
firstTimeGrouped = false
// Hand over responsibility of this action back whatever
// has subscribed as the delegate to implement anything else
// for this action
tagDelegate?.customTextViewDidFinishSelection(self)
}
}
And finally use it like so
class ThemeVC: UIViewController {
// Change UITextView to CustomTextView
#IBOutlet weak var themeTextView: CustomTextView!
var tB = UIBarButtonItem()
// If you do not set up the delegate in your
// storyboard, you need to it in your code
// call this function from didLoad or something
// if needed
private func configureTextView() {
themeTextView.tagDelegate = self
}
// All your other implementation
}
extension ThemeVC: CustomTextViewTagDelegate {
func customTextViewDidStartSelection(_ textView: CustomTextView) {
navigationController?.setToolbarHidden(false,
animated: false)
tB.isEnabled = false
}
func customTextViewDidFinishSelection(_ textView: CustomTextView) {
navigationController?.setToolbarHidden(true,
animated: false)
tB.isEnabled = true
}
}
I did not add addTagSelectorToolBar as part of the CustomTextView implementation as this is not a good candidate to be part of that module as all of its code is related to the view controller so i don't recommend making a part of the CustomTextView implementation.

Complex navigation system (and NavBar) between view controllers with the same information

I need help regarding a problem that I have been wanting to solve for a long time.
It is a navigation between view controllers (children) that take information from the parent. The parent is updated if there is a notification. And I want the children to update instantly along with the parent.
It would be a Home controller where I show basic information.
Then a button inside the Home controller that would take the View Controller that has the navigation that I mentioned.
I attach a basic scheme so you can understand it better
I would like to receive an idea even if it is basic in a programmatic way to start thinking about logic.
It would also help me a lot how to make the TOP NAVIGATION BAR between steps (step 1, step 2...), and when I click on any step that opens the child corresponding to that step.
The User type is nothing more than a dictionary with Strings, Ints and some data. (I can did the notification system and works, i only mention that can work in this way)
I have also thought that when I press the home controller button it will load 4 view controllers (one for each step) and it will consume load time, right?
Any help would get me out of stress
Thanks a lot!!
// Home controller
class HomeController: UIViewController {
let showSteps: UIButton = {
let button = UIButton(type: .system)
button.addTarget(self, action: #selector(showSteps), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
addSubview(showSteps)
}
#objc func showSteps() {
let controller = ShowSteps()
self.present(controller, animated: true, completion: nil)
}
}
// UIViewController with Navigation Bar
class ShowSteps: UIViewController {
private var user: User?
override func viewDidLoad() {
// update the User variable
Service.shared.fetchUserData(uid: "XXX") { user in
self.user = user
}
// listen notifications to update the user
let nc = NotificationCenter.default
nc.addObserver(self, selector: #selector(updateUser), name: NSNotification.Name(rawValue: "updateUser"), object: nil)
/// 1. HERE I NEED TO ADD A NAVIGATION BAR IN THE TOP OF THE VIEW, THAT APPEAR IN ALL CHILDS
/// 2. I NEED TO NAVIGATE TO OTHER STEPS WITH A FUNCTION
}
#objc func dissmissView(){
/// 3. how can i remove from view all childs 4 in this case. but can be 6 o 7.... And then dismiss actual ShowSteps
self.dismiss(animated: true, completion: nil)
}
#objc func updateUser() {
// update user if notification called
Service.shared.fetchUserData(uid: "XXX") { user in
self.user = user
}
}
}
// that is a child example
class VCexample1: UIViewController {
/// 4. how can i see the navigation bar that has the parent? How can i send to other step from here?
/// 5. how can i get the user variable from ShowSteps?
/// 6. how can i navigate to other step from here?
///
let BarButton: UIButton = {
let button = UIButton(type: .system)
button.addTarget(self, action: #selector(goToOtherStep), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
/// show user data
}
}
// that is a other child example
class VCexample2: UIViewController {
...
}
// that is a other child example
class VCexample3: UIViewController {
...
}
// that is a other child example
class VCexample4: UIViewController {
...
}
IS BASIC BASIC CODE. ITS NOT PERFECT. I HAVE PUT IT WITH ERRORS BUT SHORT SO THAT IT IS BETTER UNDERSTOOD
Simple example of delegation :
// User data
struct UserData {
var name: String
}
// this how child get info from parentVC
protocol childVCDelegate {
// delegete user func or var (only one may be usefull)
func getUserData() -> UserData
func getUserName() -> String
var userData: UserData {get set}
}
// the parent view controller who keep the user data
class ParentVC: UIViewController, childVCDelegate {
var userData: UserData = UserData(name: "nacho111")
func getUserData() -> UserData {
userData
}
func getUserName() -> String {
userData.name
}
}
// child VC with a label updated with user name from parentVC
class ChildVC: UIViewController {
var label = UILabel()
var delegate: childVCDelegate?
// either a copy of user in child view
var userInChildVC: UserData?
// either access user in Parent VC
var userInParentVC: UserData? {
delegate?.userData
}
// fonction to register in notification center to get userData changes from parentVC
func userDataChanged() {
// only one of the 2 options is usefull
userInChildVC = delegate?.getUserData()
label.text = userInChildVC?.name
label.text = delegate?.userData.name
label.text = delegate?.getUserName()
}
}
// the tab bar which create a childVC and connect it to the parent
// via delegate property of ChildVC
class TabBar: UIViewController {
var childVC: [ChildVC] = []
// visible child VC
var visibleChildVC: Int? {
willSet {
if let index = visibleChildVC {
childVC[index].view.isHidden = true
}
}
didSet {
if let index = visibleChildVC {
childVC[index].view.isHidden = false
}
}
}
func createChildVC(withParentVC parentVC: ParentVC,
visible: Bool = false) {
let ch1 = ChildVC()
ch1.delegate = parentVC
childVC.append(ch1)
// adding child VC view in view hierarchy : optional
view.addSubview(ch1.view)
if visible {
visibleChildVC = childVC.count - 1
} else {
ch1.view.isHidden = true
}
}
func displayChildVC(atIndex index: Int) {
visibleChildVC = index
}
}

SwiftUI searchbar

I'm trying to build an App which can display different pdfs via sheet. So far so good, but I want the user to be able to filter the Button which triggers the sheet modifier via a searchbar. This is what doesn't work for me. I tried different solutions from web and Stack Overflow, but none of them worked for me. I think I have to filter the Text from the Buttons which trigger the Boolean for sheet. But how? With a List or a ForEach I can't Buttons with every time different var/Bool.
Below is my Searchbar class, with just a List and filtered it works.
class SearchBar: NSObject, ObservableObject {
let searchController: UISearchController = UISearchController(searchResultsController: nil)
#Binding var text: String
let hide : Bool
let placeholder : String
let cancelButton : Bool
let autocapitalization : UITextAutocapitalizationType
init(text: Binding<String>, hide: Bool, placeholder: String, cancelButton: Bool, autocapitalization: UITextAutocapitalizationType) {
self._text = text
self.hide = hide
self.placeholder = placeholder
self.cancelButton = cancelButton
self.autocapitalization = autocapitalization
super.init()
self.searchController.obscuresBackgroundDuringPresentation = false
self.searchController.searchResultsUpdater = self
self.searchController.hidesNavigationBarDuringPresentation = hide
self.searchController.automaticallyShowsCancelButton = cancelButton
self.searchController.searchBar.placeholder = placeholder
self.searchController.searchBar.autocapitalizationType = autocapitalization
}
}
extension SearchBar: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
// Publish search bar text changes.
if let searchBarText = searchController.searchBar.text {
self.text = searchBarText
}
}
}
struct SearchBarModifier: ViewModifier {
let searchBar: SearchBar
func body(content: Content) -> some View {
content
.overlay(
ViewControllerResolver { viewController in
viewController.navigationItem.searchController = self.searchBar.searchController
}
.frame(width: 0, height: 0)
)
}
}
extension View {
func add(_ searchBar: SearchBar) -> some View {
return self.modifier(SearchBarModifier(searchBar: searchBar))
}
}
My suggestion is to replace the Binding with an action which calls a closure and passes the entered text.
Replace the line #Binding var text: String with
var action : ((String) -> Void)?
Replace updateSearchResults with
func updateSearchResults(for searchController: UISearchController) {
if let searchBarText = searchController.searchBar.text {
DispatchQueue.main.async { self.action?(searchBarText) }
}
}
and the ViewModifier and the view extension with
struct SearchBarModifier: ViewModifier {
let searchBar: SearchBar
let action : ((String) -> Void)?
func body(content: Content) -> some View {
content
.overlay(
ViewControllerResolver { viewController in
searchBar.action = action
viewController.navigationItem.searchController = self.searchBar.searchController
}
.frame(width: 0, height: 0)
)
}
}
extension View {
func add(_ searchBar: SearchBar, action : ((String) -> Void)?) -> some View {
return self.modifier(SearchBarModifier(searchBar: searchBar, action: action))
}
}
In the view you can add the search bar with
.add(self.searchBar, action: { query in
// do something with query
})
Any time the user presses a key the closure is being called.
If you want to call the closure once when the user presses the Search/Go key you have to implement searchBarSearchButtonClicked and add another action.

Swift - Dynamically added action doesn't fire

I have a function which should "toggle" a bar button item by changing between 2 images.
class Buttons {
func ToggleBarButton(button : UIBarButtonItem, name : String, location : BarButtonLocation, isEnabled : Bool, viewController : UIViewController) {
var iconName = name
if (!isEnabled) {
iconName += "EnabledIcon"
} else {
iconName += "DisabledIcon"
}
let newIcon = UIImage(named: iconName)
let newButton = UIBarButtonItem(image: newIcon, style: .Plain, target: self, action: button.action);
switch location {
case BarButtonLocation.Left:
viewController.navigationItem.leftBarButtonItem = newButton;
viewController.navigationItem.leftBarButtonItem?.tintColor = UIColor.blackColor();
case BarButtonLocation.SecondLeft:
viewController.navigationItem.leftBarButtonItems?[1] = newButton
viewController.navigationItem.leftBarButtonItems?[1].tintColor = UIColor.blackColor()
default:
return;
}
}
}
I also have a view controller class, in which there is the action of the bar button item.
class GradesViewController: UIViewController {
var isFilterEnabled = false
var isViewEnabled = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func filterButton_Pressed(sender: UIBarButtonItem) {
Buttons().ToggleBarButton(sender, name : "Filter", location: BarButtonLocation.Left, isEnabled: isFilterEnabled, viewController: self);
isFilterEnabled = !isFilterEnabled;
}
#IBAction func viewButton_Pressed(sender: UIBarButtonItem) {
Buttons().ToggleBarButton(sender, name : "View", location: BarButtonLocation.SecondLeft, isEnabled: isViewEnabled, viewController: self);
isViewEnabled = !isViewEnabled;
}
}
On first press it successfully changes the image to the enabled form, but on second press it doesn't do anything (press event doesn't even fire). I checked, and button.action is correctly identified as "filterButton_Pressed:". What's the problem, or is there an easier way to do this? Thanks for the answer in advance.
Put the break statement after each case and try.
And also remove the semi colons.
I just realized the problem was that I copied the code from the view controller to the button class, and didn't change target: self to target: viewController. But thanks for all the answers anyways...

Resources