I have drawer controller presenting menu in the iOS app.
This menu is toggled by pressing menu buttons (UIButton) available on each screen.
As you can see in the mock: menu buttons can have red dot showing that new content is available - for this case I simply have two images for menu button without dot and with it.
I thought about making custom UIControl with "global" property for this dot. Is it the right way?
class MenuButton : UIButton {
static var showNotificationDot : Bool = false
}
For example you could create subclass UIButton and add observer.
class MyButton: UIButton {
static let notificationKey = NSNotification.Name(rawValue: "MyButtonNotificationKey")
override init(frame: CGRect) {
super.init(frame: frame)
self.subcribeForChangingState()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
fileprivate func subcribeForChangingState() {
NotificationCenter.default.addObserver(forName: MyButton.notificationKey, object: nil, queue: nil) { notificaton in
if let state = notificaton.object as? Bool {
self.changeState(active: state)
}
}
}
fileprivate func changeState(active: Bool) {
//change ui of all instances
print(active)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
And change UI from any place like this:
NotificationCenter.default.post(name: MyButton.notificationKey, object: true)
Related
I create a button that when you hold, it will show the standard ios menu button for pasting a text, but I'm getting an error saying There can only be one UIMenuController instance. when I hold the button 2 times, how can I fix this?
Here is my code
override init(frame: CGRect) {
super.init(frame: frame)
self.configureView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.configureView()
}
private func configureView() {
guard let view = self.loadViewFromNib(nibName: "CustomView") else { return }
view.frame = self.bounds
self.addSubview(view)
button.addTarget(self, action: #selector(holdButton), for: .touchDown)
}
#objc func holdButton(_ sender: UIButton) {
let menuController = UIMenuController()
menuController.setTargetRect(sender.frame, in: charTextField)
menuController.setMenuVisible(true, animated: true)
}
Also, how can I listen to the user when he clicked the paste button?
I want it to call this function when he clicked paste.
func pasteClick() {
print("pasted", clipboardString())
}
Use the default singleton instance provided (.shared) by UIMenuController instead of creating an instance of your own.
#objc func holdButton(_ sender: UIButton) {
UIMenuController.shared.setTargetRect(sender.frame, in: charTextField)
UIMenuController.shared.setMenuVisible(true, animated: true)
}
Quoting from apple doc:
The singleton UIMenuController instance is referred to as the editing
menu.....
I'm creating an app with swift.
I've made child classes from UIView. After making them and writing some processes there, I feel that I want them to detect touch events.
But they aren't children of UIButton.
I'd not like to force them to detect touch events using UIGestureRecognizer. Because UIGestureRecognizer needs to be used in UIViewController. I'd like to write codes of detecting touch just in the view.
Are there any ways to detect touch events just in UIView?
You can simply add the gesture to the subclass of UIView as other said, but if you want to include the gesture within the definition of the subclass and make it more modular, you can use the notification dispatch mechanism to broadcast the gesture to the registered view controller.
First, you create a name for the notification:
extension Notification.Name {
static let CustomViewTapped = Notification.Name("CustomViewTapped")
}
Then, you add the gesture to your custom view:
class CustomView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
let tap = UIGestureRecognizer(target: self, action: #selector(tapped))
self.addGestureRecognizer(tap)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
#objc func tapped(_ sender: UIGestureRecognizer) {
NotificationCenter.default.post(name: .CustomViewTapped, object: self)
}
}
And, finally, observe the broadcast from your view controller:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(customViewTapped), name: .CustomViewTapped, object: nil)
let customView = CustomView()
self.view.addSubview(customView)
}
#objc func customViewTapped(_ sender: UIGestureRecognizer) {
}
}
You can add UIGestureRecognizer to UIView. Another way you can add invisible UIButton on top of your UIView.
If you use UIView subclass, you can use something following and handle tap in action closure
class TappableView: UIView {
var action: (()->())? = nil
init(frame: CGRect) {
super.init(frame: frame)
initialization()
}
func initialization() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
addGestureRecognizer(tapGesture)
}
#objc private func tapGesture() {
action?()
}
}
class childView: UIView {
var action: (()->())? = nil
init(frame: CGRect) {
super.init(frame: frame)
initialization()
}
func initialization() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
addGestureRecognizer(tapGesture)
}
#objc private func tapGesture() {
//Action called here
}
}
//MARK:- View Tap Handler
extension UIView {
private struct OnClickHolder {
static var _closure:()->() = {}
}
private var onClickClosure: () -> () {
get { return OnClickHolder._closure }
set { OnClickHolder._closure = newValue }
}
func onTap(closure: #escaping ()->()) {
self.onClickClosure = closure
isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(onClickAction))
addGestureRecognizer(tap)
}
#objc private func onClickAction() {
onClickClosure()
}
}
Usage:
override func viewDidLoad() {
super.viewDidLoad()
let view = UIView(frame: .init(x: 0, y: 0, width: 80, height: 50))
view.backgroundColor = .red
view.onTap {
print("View Tapped")
}
}
I'm subclassing UITextView and want to handle user input inside it. It's not an option to take advantage of delegation because it should be possible to set a delegate to something different. Anyone knows how can I accomplish this?
There is a workaround for this. You can use UITextViewTextDidChange notification.
class UITextViewPlus: UITextView {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
NotificationCenter.default.addObserver(self, selector: #selector(textChange(_:)), name: .UITextViewTextDidChange, object: nil)
}
func textChange(_ sender: NSNotification) {
guard let textView = sender.object as? UITextViewPlus, textView == self else {
// ignoring text change of any other UITextView
return
}
// do something
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
Note: Just keep in mind UITextViewTextDidChange notification is posted for any text change in any UITextView.
In my main app view which contains both a UITextField and a UITableView, I have the "usual" code using a UITapGestureRecognizer to dismiss the virtual keyboard if a tap is detected outside of the keyboard while I'm editing the contents of the UITextField.
An added feature is that this is only enabled if the virtual keyboard is actually shown - I don't want "background taps" to cause editing to end if the virtual keyboard isn't visible, but nor do I want background taps to trigger their normal behaviour - they should be consumed if the virtual keyboard is currently showing.
override func viewDidLoad() {
...
tapper = UITapGestureRecognizer(target: self, action: #selector(viewTapped))
NotificationCenter.default.addObserver(self, selector: #selector(keyboardShown), name:
NSNotification.Name(rawValue: "UIKeyboardDidShowNotification"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardHidden), name:
NSNotification.Name(rawValue: "UIKeyboardDidHideNotification"), object: nil)
}
#IBAction func keyboardShown(_ sender: AnyObject) {
view.addGestureRecognizer(tapper!)
}
#IBAction func keyboardHidden(_ sender: AnyObject) {
view.removeGestureRecognizer(tapper!)
}
#IBAction func viewTapped(_ sender: AnyObject) {
view.endEditing(false)
}
This mostly works, except that the UITableView has interactive header cells which each also have a UITapGestureRecognizer attached.
The net result is that if I click on a header cell the gesture recognizer on that cell gets fired, and not the one on the parent view, and the keyboard doesn't get dismissed. If I click on the data cells instead, everything works fine.
If it matters, my UITableView has its own UIViewController subclass and is contained in a nested UIView - the table is too complicated to have that code in my main view controller.
How can I prevent the sub-view's gesture recognizers from handling these taps when the parent view's recognizer is attached so that the parent view can handle them instead?
I've implemented what I consider a "temporary" solution by also observing the virtual keyboard notifications in the UITableView's controller, tracking the keyboard visibility state, and then implementing this UIGestureRecognizerDelegate method on the header cells' gesture recognizer:
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return !keyboardShowing
}
This duplicates a certain amount of functionality from the main view in the sub-view which really shouldn't need to know about the state of the keyboard. I'm still looking for a method that can be handled entirely from within the parent view.
EDIT - with thanks to #Tommy for the hint, I now have a better solution that removes any need to track the keyboard state in the sub-view.
My parent view no longer uses a UIGestureRecognizer, but instead uses a custom subclass of UIView to track touch events, and conditionally ignore them:
class KeyboardDismissingView: UIView {
private var keyboardVisible = false
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let r = super.hitTest(point, with: event) else { return nil }
var v : UIView! = r
while v != nil {
if v is UITextField {
return r
}
v = v.superview
}
if keyboardVisible {
self.endEditing(false)
return nil
}
return r
}
func setup() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardShown), name:
NSNotification.Name(rawValue: "UIKeyboardDidShowNotification"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardHidden), name:
NSNotification.Name(rawValue: "UIKeyboardDidHideNotification"), object: nil)
}
#IBAction func keyboardShown(_ sender: AnyObject) {
keyboardVisible = true
}
#IBAction func keyboardHidden(_ sender: AnyObject) {
keyboardVisible = false
}
override init (frame: CGRect) {
super.init(frame: frame)
setup()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
setup()
}
}
I have a drawing app. Inside my VC there are five imageViews with five colors in them. I want to be able to click on the imageView and change the stroke color. It can be easily done if I repeat myself in the viewcontroller by adding gesture Recognizers to each UIImageView and have their individual "selector" function. Such as
func redTapped() {}
func blueTapped() {}
However, I want to be able to make the code more clear by creating a custom class (ColorImageView.Swift) for these ImageViews so that when I assign the class to these buttons, they automatically gets the tap gesture and my VC automatically receives the information about which one is tapped. At the moment, I can get a "imagePressed" printed out for each image that gets assigned to my class. However, I have no way of distinguishing which one were pressed. Below are my code for ColorImageView.Swift
import Foundation
class ColorImageView: UIImageView {
private func initialize() {
let touchGesture = UITapGestureRecognizer(target: self, action: #selector(ColorImageView.imagePressed(_:)))
touchGesture.numberOfTapsRequired = 1
self.userInteractionEnabled = true
self.addGestureRecognizer(touchGesture)
}
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
func imagePressed(gestureRecognizer: UITapGestureRecognizer) {
print("image pressed \(gestureRecognizer)")
}
}
My imageView names are red.png, green.png, blue.png...etc
Thanks
You can get the tag easily.It works fine.
func imagePressed(gestureRecognizer: UITapGestureRecognizer)
{
print("image pressed \(gestureRecognizer)")
let tappedImageVIew = gestureRecognizer.view as! UIImageView
print("image pressed \(tappedImageVIew.tag)")
}