I tried calling tabBarController!.tabBar.hidden = true in viewDidLoad() and it hides the TabBar. However, I tried to set tap gesture and hide the bar on Tap. The parent viewController that has ScrollView inside it with subview (that is connected with IBOutlet as myView)
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
myView.addGestureRecognizer(tap)
}
func handleTap(sender: UITapGestureRecognizer? = nil) {
print("A") // logs successfully
if TabBarHidden == false {
print("B") // logs successfully
//I tried:
tabBarController?.tabBar.hidden = true
// I also tried
tabBarController?.tabBar.alpha = 0
tabBarController?.tabBar.frame.origin.x += 50
hidesBottomBarWhenPushed = true
} else {
...
TabBarHidden = false
}
}
hidden does work when I call it in viewDidLoad as I said, but not if I call in tap gesture function. What may be the problem? What am I missing?
this code totally works for me:
class ViewController: UIViewController {
var tabBarHidden: Bool = false {
didSet {
tabBarController?.tabBar.hidden = tabBarHidden
}
}
override func viewDidLoad() {
super.viewDidLoad()
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapGestureRecognized(_:)))
view.addGestureRecognizer(tapGestureRecognizer)
}
func tapGestureRecognized(sender: UITapGestureRecognizer) {
tabBarHidden = !tabBarHidden
}
}
Related
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.
When I am tapping on UIView but Gesture is not working.
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
viewForSHadow.isUserInteractionEnabled = true
viewForSHadow.addGestureRecognizer(tap)
// Do any additional setup after loading the view.
}
func handleTap(_sender: UITapGestureRecognizer) {
print("---------View Tapped--------")
viewForSHadow.isHidden = true
viewForAlert.isHidden = true
}
I just want to perform this action on UIView tap.
You may check in the debug view hierarchy if anything with alpha = 0 is overlapping your viewForSHadow.
You have to implement as follows:
class ViewController: UIViewController, UIGestureRecognizerDelegate {
...
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(_sender:)))
viewForSHadow.isUserInteractionEnabled = true
viewForSHadow.addGestureRecognizer(tap)
}
#objc func handleTap(_sender: UITapGestureRecognizer) {
print("---------View Tapped--------")
viewForSHadow.isHidden = true
viewForAlert.isHidden = true
}
...
}
Your code is more than enough to have working UIGestureRecognizer, you should check some other stuff like, is there something else that can consume the user interaction. And also to check if you use
isUserInteractionEnabled = false
to some parent view of viewForSHadow.
You have to use UIGestureRecognizerDelegate
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDismiss))
tapRecognizer.delegate = self
blackView.addGestureRecognizer(tapRecognizer)
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return true
}
#objc func handleDismiss() {
print("handleDismiss")
}
Reference
Don't forget to set the UIGestureRecognizerDelegate in your class
You need to mention numberOfTapsRequired property of the UITapGestureRecognizer.
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture(_sender:)))
tap.numberOfTapsRequired = 1
viewForSHadow.isUserInteractionEnabled = true
viewForSHadow.addGestureRecognizer(tap)
}
#objc func handleTapGesture(_sender: UITapGestureRecognizer) {
print("---------View Tapped--------")
// Why are you hiding this view **viewForSHadow**
viewForSHadow.isHidden = true
viewForAlert.isHidden = true
}
Also, make sure you are not doing viewForSHadow.isUserInteractionEnabled = false anywhere in the code.
First of all you code doesn't compile. The handleTap(_:) signature must be like:
#objc func handleTap(_ sender: UITapGestureRecognizer) {
print("---------View Tapped--------")
}
Secondly, you need to first try with the minimal code in a separate project. And by minimal code I mean what you've added in the question.
class ViewController: UIViewController {
#IBOutlet weak var viewForSHadow: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
viewForSHadow.addGestureRecognizer(tap)
}
#objc func handleTap(_ sender: UITapGestureRecognizer) {
print("---------View Tapped--------")
}
}
Try with just the above code and see if you can get it working. The above code is working well at my end without any delegate or numberOfTaps.
I have a UIView that is showed every time I press a button in another view
#IBOutlet weak var view1: UIView!
#IBOutlet weak var view2: UIView!
#IBAction func showView(_ sender: Any) {
view2.isHidden = false
}
What I want is to add a tap gesture that allows me to hide view2 every time I tap outside of the view and, since those views are draggable, I want the second view not to be tappable when hidden ( so that if I touch under my view I don't risk to move it.
This is what I tried:
var gesture : UITapGestureRecognizer?
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self,
selector: #selector(closeView), name: NSNotification.Name("CloseView"), object: nil)
gesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.closeView))
}
#objc func closeView() {
if view2.isHidden == false {
view2.isUserInteractionEnabled = true
view2.isHidden = false
self.view.removeGestureRecognizer(gesture!)
} else {
view2.isHidden = true
view2.isUserInteractionEnabled = true
self.view.addGestureRecognizer(gesture!)
}
}
let closeTapGesture = UITapGestureRecognizer(target: view, action: #selector(getter: view2.isHidden)
view.addGestureRecognizer(closeTapGesture)
None of this work, how can I do?
You need to check if you actually tapped outside of view2:
var gesture : UITapGestureRecognizer?
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self,
selector: #selector(closeView), name: NSNotification.Name("CloseView"), object: nil)
let gesture = UITapGestureRecognizer(target: self, action: #selector(closeView(_:)))
view.addGestureRecognizer(gesture)
self.gesture = gesture
}
#objc private func closeView(_ tapGestureRecognizer: UITapGestureRecognizer) {
let location = tapGestureRecognizer.location(in: view2)
guard view2.isHidden == false,
!view2.bounds.contains(location) else { //We need to have tapped outside of view 2
return
}
view2.isHidden = true
}
Your tap gesture should only handle closeView .
#objc func closeView() {
view2.isHidden = true
view2.isUserInteractionEnabled = false
gesture?.isEnabled = false
}
And the the button click to show your view2 should call this.
func showView() {
view2.isHidden = false
view2.isUserInteractionEnabled = true
gesture?.isEnabled = true
}
I have implemented the new SearchController with its searchBar and the searchResultsController.
Here is how I implemented it :
The resultViewController:
lazy var resultViewController: SearchResultViewController = {
let storyboard = UIStoryboard.init(name: "Main", bundle: nil)
let searchResultViewController = storyboard.instantiateViewController(withIdentifier: "SearchResultViewController") as! SearchResultViewController
searchResultViewController.delegate = self
return searchResultViewController
}()
And this is the SearchController:
lazy var searchController: UISearchController = {
let searchController = UISearchController(searchResultsController: resultViewController)
searchController.searchBar.delegate = self
searchController.obscuresBackgroundDuringPresentation = true
searchController.searchResultsUpdater = self
searchController.searchBar.placeholder = "Search.city.label".localizable()
searchController.searchBar.tintColor = UIColor.white
searchController.searchBar.barTintColor = UIColor.white
UITextField.appearance(whenContainedInInstancesOf: [type(of: searchController.searchBar)]).tintColor = UIColor(red:0.00, green:0.47, blue:0.78, alpha:1.0)
if let textfield = searchController.searchBar.value(forKey: "searchField") as? UITextField {
if let backgroundview = textfield.subviews.first {
// Background color
backgroundview.backgroundColor = UIColor.white
// Rounded corner
backgroundview.layer.cornerRadius = 10;
backgroundview.clipsToBounds = true;
}
}
definesPresentationContext = true
return searchController
}()
In my viewWillAppear I set the navigationItem.searchController :
self.searchController.isActive = true
if #available(iOS 11.0, *) {
self.navigationItem.searchController = searchController
self.navigationItem.hidesSearchBarWhenScrolling = true
} else {
// Fallback on earlier versions
}
I have been able to handle the cancelButtonClicked :
extension HomeViewController: UISearchBarDelegate {
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.endEditing(true)
self.searchController.isActive = false
}
}
This is doing the "cancel" animation, hiding keyboard + inactive state on searchBar/searchController. Both at the same time, with 1 tap on cancel Button.
But I am unable to achieve this when the user tap anywhere on the view.
I tried with tap gesture but it requires me 2 tap to achieve the same behavior.
NB:
I got an UICollectionView in my UIViewController, which takes all the place in the UIView.
Here is what I have tried :
override func viewDidLoad() {
super.viewDidLoad()
handleTapAnywhereToRemoveKeyboard()
}
func handleTapAnywhereToRemoveKeyboard() {
let singleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.singleTap(sender:)))
//singleTapGestureRecognizer.numberOfTapsRequired = 1
singleTapGestureRecognizer.cancelsTouchesInView = false
self.view.addGestureRecognizer(singleTapGestureRecognizer)
}
#objc func singleTap(sender: UITapGestureRecognizer) {
self.searchController.isActive = false
self.searchController.searchBar.resignFirstResponder()
self.searchController.searchBar.endEditing(true)
}
EDIT:
I was thinking, maybe it's because my searchBar and searchController aren't in the UIViewController's view hierarchy, but more in the NavigationController one.
So I also tried with :
navigationController?.view.endEditing(true)
I then was thinking, maybe it's because the UIScrollView within my UICollectionView is catching the tap.
So I tried to link the tap gesture on the UICollectionView instead of the UIView, but without success.
There is no need to add UITapGestureRecognizer as proposed above. UIViewContoller already conforms to UIResponder interface (legacy from Objective C), so you can override this method like this:
extension UIViewController {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.window?.endEditing(true)
super.touchesEnded(touches, with: event)
}
}
Please create UIViewController for dismiss keyboard througout application.
extension UIViewController {
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
#objc func dismissKeyboard() {
view.endEditing(true)
}
}
Use above code in your UIViewController file as below.
override func viewDidLoad() {
super.viewDidLoad()
self.hideKeyboardWhenTappedAround()
}
Update:
You can also use below code to dismiss keyboard from any class.
UIApplication.shared.sendAction(#selector(UIResponder.resign‌​FirstResponder), to: nil, from: nil, for: nil)
Try the solution from Dimple Desai but add the gesture recognizer to the TableView or CollectionView or whatever is laying on top of your UIView. Then it should work.
override func viewDidLoad(){
let tapView = UITapGestureRecognizer(target: self, action: #selector(self.hideKeyboard))
self.view.addGestureRecognizer(tapView)
}
#objc func hideKeyboard(tap: UITapGestureRecognizer){
self.view.endEditing(true)
}
I need UITapGestureRecognizer to both hide and unhide the value. User with single tap should hide the label value and with the single tap should unhide the label, is there any way I could perform this operation?
Now I have used tap and long-tap gesture to perform this operation. Below is my code,
let tab = UITapGestureRecognizer(target: self, action: #selector(availabelBalance))
tab.numberOfTapsRequired = 1
tab.cancelsTouchesInView = false
accountBalanceView.addGestureRecognizer(tab)
let tabTwo = UILongPressGestureRecognizer(target: self, action: #selector(availabelBalanceTwo))
accountBalanceView.addGestureRecognizer(tabTwo)
If you hide the label then you will not able to touch again that label as it is hidden now.
To hide a label you can you this trick.
When you tap on label then you can check that...
var tempText = "" //temperory property to store value or label
#objc func tapDetected(_ sender: UITapGestureRecognizer) {
if let text = label.text, !text.isEmpty {
tempText = lbl.text.text
label.text = " "
} else {
label.text = tempText
}
}
You only need tapGesture
let tab = UITapGestureRecognizer(target: self, action: #selector(availabelBalance(_:)))
tab.numberOfTapsRequired = 1
tab.cancelsTouchesInView = false
accountBalanceView.addGestureRecognizer(tab)
//
#objc func availabelBalance(_ sender:UITapGestureRecognizer) {
if lbl.text == "" {
lbl.text = value
}
else {
lbl.text = ""
}
}
Here is sample code
import UIKit
class firstViewController: UIViewController {
#IBOutlet var textLbl: UILabel!
var tab : UITapGestureRecognizer?
override func viewDidLoad() {
super.viewDidLoad()
tab = UITapGestureRecognizer(target: self, action: #selector(availabelBalance))
tab?.numberOfTapsRequired = 1
tab?.cancelsTouchesInView = false
self.view.addGestureRecognizer(tab!)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#objc func availabelBalance(_ sender:UITapGestureRecognizer) {
if (tab?.cancelsTouchesInView)! {
textLbl.isHidden = true
tab?.cancelsTouchesInView = false
}else{
textLbl.isHidden = false
tab?.cancelsTouchesInView = true
}
}
}