Only want Copy and Share from UIMenuController, but code still being wonky - ios

Here's what my textView looks like right now. It is a textview inside a scrollview.
I am trying to replace the usual UIMenuController menu items with Save and Delete but not getting there. Can someone help me out?
Here's my code:
import UIKit
class DetailViewController: UIViewController, UIGestureRecognizerDelegate, {
var selectedStory : URL!
#IBOutlet weak var textView: UITextView!
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var textSlider: UISlider! {
didSet {
configureSlider()
}
}
override func viewDidLoad() {
super.viewDidLoad()
let storyText = try? String(contentsOf: selectedStory)
textView.text = storyText
textView.isUserInteractionEnabled = true
let longPressGR = UILongPressGestureRecognizer(target: self, action: #selector(longPressHandler))
longPressGR.minimumPressDuration = 0.3 //
textView.addGestureRecognizer(longPressGR)
}
// MARK: - UIGestureRecognizer
#objc func longPressHandler(sender: UILongPressGestureRecognizer) {
guard sender.state == .began,
let senderView = sender.view,
let superView = sender.view?.superview
else { return }
senderView.becomeFirstResponder()
UIMenuController.shared.setTargetRect(senderView.frame, in: superView)
UIMenuController.shared.setMenuVisible(true, animated: true)
}
override var canBecomeFirstResponder: Bool {
return true
}
}
extension UITextView{
override open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == Selector(("_copy:")) || action == Selector(("_share:"))
{
return true
} else {
return false
}
}
}
extension UIScrollView{
override open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == Selector(("_copy:")) || action == Selector(("_share:"))
{
return true
} else {
return false
}
}
}
I'm getting 2 issues:
When I tap the screen, only the Share is showing up and the Copy is not.
The Share button shows up randomly near the center, not on the text that is selected, like so.

First of all, remove UITextView that is inside UIScrollView because UIScrollView itself is the parent class of UITextView. It will place the UIMenuController at appropriate frame.
Remove longPressGR and longPressHandler methods.
Replace this method,
extension UITextView{
override open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action.description == "copy:" || action.description == "_share:" {
return true
} else {
return false
}
}
}
You will get following output.

Related

Swift Class has another class from Pod with UiViewController and get Nil on outlets because of that

First of all, sorry the english.
I have a project in Swift 4.2. One of the Pods is imported and added to a class "Principal View Controller". This Pod has de UIViewController and i cannot add it on the class because it's used on the Pod and Xcode doesn't alow multiple inheritance.
If I remove the Pod from the class the Iboutlets works without problem but the pod is not working ( it's used for side menu ). If i leave the Pod added, the Iboutlets just get nil value.
I track in a debug the code and pass on 2 of the ViewDidLoad and nothing load value on Outlets if Pod is added.
Maybe someone knows the way to fix this?
**I remove some code from classes because it's way more than the 300000 characters that stackoverflow allow...
//
// PrincipalViewController.swift
//
import UIKit
import SwiftyJSON
import JLSlideMenuController
import Alamofire
class PrincipalViewController: JLSlideNavigationController, UIImagePickerControllerDelegate, URLSessionDownloadDelegate, UIGestureRecognizerDelegate {
#IBOutlet weak var scrollViewWeather: UIScrollView!
#IBOutlet var profileImage:UIImageView!
#IBOutlet var parentView: UIView!
#IBOutlet var lblUserName:UILabel!
#IBOutlet var menuButton:UIButton!
var jsonVar:JSON!
var rutasDisponibles : [Ruta]!
var userDefaults = UserDefaults.standard
var viewDayArray = [UIView](repeating: UIView(), count: 7)
var labelDayArray = [UILabel](repeating: UILabel(), count: 7)
var labelTempDayArray = [UILabel](repeating: UILabel(), count: 7)
var ivDayArray = [UIImageView](repeating: UIImageView(), count: 7)
var taskDescargaKmls : URLSessionTask!
var contador = 1
#IBOutlet weak var btMisRutas: UIButton!
#IBOutlet weak var btRutas: UIButton!
#IBOutlet weak var btInfoMunicipio: UIButton!
var jlSlide: JLSlideNavigationController!
lazy var session : Foundation.URLSession = {
let config = URLSessionConfiguration.ephemeral
config.allowsCellularAccess = false
let session = Foundation.URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
return session
}()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
UIApplication.shared.isStatusBarHidden = false
profileImage.layer.borderColor = UIColor.lightGray.cgColor
let lang = UserDefaults.standard.string(forKey: "LANG");
btRutas.setTitle(Languages.translateText("rutas", language: lang!), for: UIControl.State())
btInfoMunicipio.setTitle(Languages.translateText("info_municipio", language: lang!), for: UIControl.State())
btMisRutas.setTitle(Languages.translateText("mis_rutas", language: lang!), for: UIControl.State())
if(!OnlineStatus.isConnectedToNetwork()){
scrollViewWeather.isHidden = true
}
}
#IBAction func sideMenu(_ sender: UIButton) {
if menuIsPresented(){
self.hideMenu(animated: true)
}
else{
self.showMenu(animated: true)
}
}
override func viewDidLoad() {
super.viewDidLoad()
let configuration = URLSessionConfiguration.background(withIdentifier: "com.website.background")
let gesture = UITapGestureRecognizer(target: self, action: #selector(PrincipalViewController.closeMenu(_:)))
gesture.numberOfTapsRequired = 1
gesture.numberOfTouchesRequired = 1
gesture.delegate = self
self.parentView.addGestureRecognizer(gesture)
addSlideMenu("MenuViewController",storyboardName: "Main",distToTop: 0, widthAspectRatio: 0.8, distToBottom: 0,comeFromLeft: true)
}
The pod is JSlideMenuController : https://cocoapods.org/pods/JLSlideMenuController
//
// JLSlideMenuViewController.swift
// Pods
//
// Created by José Lucas Souza das Chagas on 20/03/16.
//
//
import UIKit
public class JLSlideMenuViewController: UIViewController {
public var attachedNavController:JLSlideNavigationController!
//constraints
var sideDist:NSLayoutConstraint!
var distToTopC:NSLayoutConstraint!
var distToBottomC:NSLayoutConstraint!
var widthC:NSLayoutConstraint!
//
/**
TRUE to enable the menu FALSE to disable
The value of this enable or disable the animations and gestures to show and hide the menu
*/
public var enabled:Bool = true{
didSet{
if enabled == false{
attachedNavController.hideMenu(animated: false)
}
}
}
override public func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.view.layoutIfNeeded()
}
override public func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
//print(parent!.childViewControllers)
}
//public override func canBecomeFirstResponder() -> Bool {
public func canBecomeFirstResponder() -> Bool {
return true
}
override public func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//override public func prefersStatusBarHidden() -> Bool {
public func prefersStatusBarHidden() -> Bool {
return true
}
func removeConstraints(){
self.view.window!.removeConstraint(sideDist)
self.view.window!.removeConstraint(distToTopC)
self.view.window!.removeConstraint(distToBottomC)
self.view.removeConstraint(widthC)
}
/**
Call this method to present modally the correct view controller accordingly to some interaction
with the menu view controller
- parameter VCId: The Id of the view controller to show
- parameter storyboardName: the name of the storyboard where the view controller is
- parameter animated: true to transition with some animation or not
*/
public func presentControllerModally(VCId:String,storyboardName:String,animated:Bool){
let destinyVC = JLSlideNavigationController.loadMenuVC(identifier: VCId, storyboardName: storyboardName)
destinyVC.view.frame = self.attachedNavController.view.frame
self.attachedNavController.hideMenu(animated: false)
let menuSegue = UIStoryboardSegue(identifier: "actualViewToAnother", source: self.attachedNavController,
destination: destinyVC, performHandler: { () -> Void in
DispatchQueue.global(qos: .background).async {
// Background Thread
DispatchQueue.main.async {
// Run UI Updates
self.attachedNavController.topViewController!.present(destinyVC, animated: animated, completion: nil)
}
}
/*
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.attachedNavController.topViewController!.presentViewController(destinyVC, animated: animated, completion: nil)
})
*/
})
//self.prepareForSegue(menuSegue, sender: nil)
self.attachedNavController.topViewController!.prepare(for: menuSegue, sender: nil)
menuSegue.perform()
}
/**
Call this method to show the correct view controller accordingly to some interaction
with the menu view controller
Use this one if the view controller where the menu is right now have some navigation controller
- parameter VCId: The Id of the view controller to show
- parameter storyboardName: the name of the storyboard where the view controller is
- parameter animated: true to transition with some animation or not
*/
public func showController(VCId:String,storyboardName:String,animated:Bool){
let destinyVC = JLSlideNavigationController.loadMenuVC(identifier: VCId, storyboardName: storyboardName)
destinyVC.view.frame = self.attachedNavController.view.frame
self.attachedNavController.hideMenu(animated: false)
let menuSegue = UIStoryboardSegue(identifier: "actualViewToAnother", source: self.attachedNavController,
destination: destinyVC, performHandler: { () -> Void in
self.attachedNavController.pushViewController(destinyVC, animated: animated)
})
//self.prepareForSegue(menuSegue, sender: nil)
self.attachedNavController.prepare(for: menuSegue, sender: nil)
menuSegue.perform()
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
//
// JLSlideNavigationController.swift
// Pods
//
// Created by José Lucas Souza das Chagas on 24/04/16.
//
//
import UIKit
open class JLSlideNavigationController: UINavigationController,UINavigationControllerDelegate {
/**
menuVCStoryboardID: The Id of your menu View Controller.
*/
#IBInspectable private(set) var menuVCStoryboardID:String?
/**
the name of the storyboard where your menu View Controller is.
*/
#IBInspectable private(set) var storyboardName:String?
/**
the value of the constraint that indicates the distance between your menu top and this VC top.
*/
#IBInspectable private(set) var distToTop:CGFloat = 0
/**
the value of the constraint that determine your menu Width.
*/
#IBInspectable private(set) var width:CGFloat = 250
/**
the value of the constraint that indicates the distance between your menu bottom and this VC bottom.
*/
#IBInspectable private(set) var distToBottom:CGFloat = 0
/**
the bolean value that indicates if your menu will come from left or from right of the window.
*/
#IBInspectable private(set) var comeFromLeft:Bool = true
/**
A bolean value that indicates to use or not shadow effects
*/
#IBInspectable private(set) var useShadowEffects:Bool = false
private var menuContainerView: UIView?
/**
The instace of your menu View Controller associated to this View Controller
*/
private(set) static var myMenuVC:JLSlideMenuViewController?
public static var panGes:UIPanGestureRecognizer?
public static var screeEdgePanGes:UIScreenEdgePanGestureRecognizer?
/**
Incicates that the pan gesture that were started enabled or not
*/
private var panEnabled:Bool = false
private var lastPanTouch:CGPoint?
override open func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
if let pan = JLSlideNavigationController.panGes{
pan.isEnabled = true
}
if let pan = JLSlideNavigationController.screeEdgePanGes{
pan.isEnabled = true
}
// Do any additional setup after loading the view.
}
open override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let _ = self.view.window{
checkIfShouldAddOrUpdateMenu()
}
print(self.view.window!.gestureRecognizers!.count)
}
open override func viewDidDisappear(_ animated: Bool) {
super.viewDidAppear(animated)
if let pan = JLSlideNavigationController.panGes{
pan.isEnabled = false
}
if let pan = JLSlideNavigationController.screeEdgePanGes{
pan.isEnabled = false
}
}
class func loadMenuVC(identifier:String,storyboardName:String)->UIViewController{
let storyboard = UIStoryboard(name: storyboardName, bundle: Bundle.main)
return storyboard.instantiateViewController(withIdentifier: identifier)
}
//MARK: - NavigationController delegate methods
public func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) {
if let menuView = menuContainerView , let window = self.view.window{
window.bringSubviewToFront(menuView)
}
let subViews = viewController.view.subviews
if let screenEdgeGes = JLSlideNavigationController.screeEdgePanGes{
for subView in subViews{
if let gestures = subView.gestureRecognizers{
for gesture in gestures{
gesture.require(toFail: screenEdgeGes)
}
}
}
}
}
//MARK: - Gestures methods
private func addGestures(){
//Pan
if let window = self.view.window , let pan = JLSlideNavigationController.panGes{
window.removeGestureRecognizer(pan)
}
JLSlideNavigationController.panGes = UIPanGestureRecognizer(target: self, action: #selector(JLSlideNavigationController.panAction(panGes:)))
self.view.window?.addGestureRecognizer(JLSlideNavigationController.panGes!)
//Screen Edge pan
if let window = self.view.window , let pan = JLSlideNavigationController.screeEdgePanGes{
window.removeGestureRecognizer(pan)
}
JLSlideNavigationController.screeEdgePanGes = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(JLSlideNavigationController.screenEdgePanAction(edgePan:)))
if comeFromLeft{
JLSlideNavigationController.screeEdgePanGes!.edges = UIRectEdge.left
}
else{
JLSlideNavigationController.screeEdgePanGes!.edges = UIRectEdge.right
}
self.view.window?.addGestureRecognizer(JLSlideNavigationController.screeEdgePanGes!)
JLSlideNavigationController.panGes!.require(toFail: JLSlideNavigationController.screeEdgePanGes!)
if let screenEdgeGes = JLSlideNavigationController.screeEdgePanGes, let topViewC = topViewController{
let subViews = topViewC.view.subviews
for subView in subViews{
if let gestures = subView.gestureRecognizers{
for gesture in gestures{
gesture.require(toFail: screenEdgeGes)
}
}
}
}
}
#objc public func screenEdgePanAction(edgePan:UIScreenEdgePanGestureRecognizer){
let currenLocation = edgePan.location(in: self.view)
if let menuView = menuContainerView, JLSlideNavigationController.myMenuVC!.enabled{
if edgePan.state == UIGestureRecognizer.State.began{
panEnabled = false
if !menuIsPresented(){
panEnabled = true
lastPanTouch = currenLocation
}
}
else if edgePan.state == UIGestureRecognizer.State.ended{//at the end of pan gesture check if the menu is near to show or hide completelly and then finishes the movement
panEnabled = false
let menuFrame = menuView.frame
if comeFromLeft{
if menuFrame.origin.x <= -menuFrame.size.width/2{
self.hideMenu(animated: true)
}
else if menuFrame.origin.x > -menuFrame.size.width/2{
self.showMenu(animated: true)
}
}
else{
if menuFrame.origin.x >= self.view.frame.width - menuFrame.size.width/2{
self.hideMenu(animated: true)
}
else if menuFrame.origin.x < self.view.frame.width - menuFrame.size.width/2{
self.showMenu(animated: true)
}
}
}
if panEnabled{//if the pan started at a right point continue
menuView.alpha = 1
let xDeslocamento = currenLocation.x - lastPanTouch!.x
lastPanTouch = currenLocation
if comeFromLeft{
if menuView.frame.origin.x + xDeslocamento >= -menuView.frame.width && menuView.frame.origin.x + xDeslocamento <= 0{
menuView.frame.origin = CGPoint(x:menuView.frame.origin.x + xDeslocamento ,y:menuView.frame.origin.y)
}
}
else{
if menuView.frame.origin.x + xDeslocamento >= self.view.frame.width - menuView.frame.width && menuView.frame.origin.x + xDeslocamento <= self.view.frame.width{
menuView.frame.origin = CGPoint(x:menuView.frame.origin.x + xDeslocamento ,y: menuView.frame.origin.y)
}
}
}
}
}
}

Disable all UIResponderStandardEditActions when UITextfield is empty

I am currently trying to prevent all UIResponderStandardEditActions like copy, paste, delete from showing up when the UITextfield is empty. I would only like to show them if the user has types a message. I have tried 2 solutions and currently don't work, I'm not sure if its to do with iOS 12 or. I have tried overriding the canPerformAction method both in a UITextfield extension and using a custom class later assigned to the UITextfield in the Storyboard but no luck. Is there another way to do this. Here is what I have tried.
extension UITextField {
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if self.text!.isEmpty {
return false
}
return action == #selector(UIResponderStandardEditActions.paste(_:))
}
}
class CustomTextField: UITextField {
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(UIResponderStandardEditActions.paste(_:)) || action == #selector(UIResponderStandardEditActions.copy(_:)) || action == #selector(UIResponderStandardEditActions.delete(_:)) {
return false
}
return true
}
}
Just override with your subclass and use this class instead of UITextField. By the way, this will disable copy paste,cut for whole conditions so that you need to add some hasText: Bool or related condition to the switch case.
#IBDesignable
class ActionsDisabledUITextField: UITextField {
#IBInspectable var isPasteEnabled: Bool = false
#IBInspectable var isSelectEnabled: Bool = false
#IBInspectable var isSelectAllEnabled: Bool = false
#IBInspectable var isCopyEnabled: Bool = false
#IBInspectable var isCutEnabled: Bool = false
#IBInspectable var isDeleteEnabled: Bool = false
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
switch action {
case #selector(UIResponderStandardEditActions.paste(_:)) where !isPasteEnabled,
#selector(UIResponderStandardEditActions.select(_:)) where !isSelectEnabled,
#selector(UIResponderStandardEditActions.selectAll(_:)) where !isSelectAllEnabled,
#selector(UIResponderStandardEditActions.copy(_:)) where !isCopyEnabled,
#selector(UIResponderStandardEditActions.cut(_:)) where !isCutEnabled,
#selector(UIResponderStandardEditActions.delete(_:)) where !isDeleteEnabled:
return false
default:
//return true : this is not correct
return super.canPerformAction(action, withSender: sender)
}
}
}

Does UILabel have any value to make it selectable?

Does UILabel have any value that can be set in order to make it selectable?
I have a label that I want to be selectable, (long press and a copy btn shows up) kinda like in Safari.
Self-contained Solution (Swift 5)
You can adapt the solution from #BJHSolutions and NSHipster to make the following self-contained SelectableLabel:
import UIKit
/// Label that allows selection with long-press gesture, e.g. for copy-paste.
class SelectableLabel: UILabel {
override func awakeFromNib() {
super.awakeFromNib()
isUserInteractionEnabled = true
addGestureRecognizer(
UILongPressGestureRecognizer(
target: self,
action: #selector(handleLongPress(_:))
)
)
}
override var canBecomeFirstResponder: Bool {
return true
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return action == #selector(copy(_:))
}
// MARK: - UIResponderStandardEditActions
override func copy(_ sender: Any?) {
UIPasteboard.general.string = text
}
// MARK: - Long-press Handler
#objc func handleLongPress(_ recognizer: UIGestureRecognizer) {
if recognizer.state == .began,
let recognizerView = recognizer.view,
let recognizerSuperview = recognizerView.superview {
recognizerView.becomeFirstResponder()
UIMenuController.shared.setTargetRect(recognizerView.frame, in: recognizerSuperview)
UIMenuController.shared.setMenuVisible(true, animated:true)
}
}
}
Yes, you need to implement a UIMenuController from a long press gesture applied to your UILabel. There is an excellent article about this on NSHipster, but the gist of the article is the following.
Create a subclass of UILabel and implement the following methods:
override func canBecomeFirstResponder() -> Bool {
return true
}
override func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool {
return (action == "copy:")
}
// MARK: - UIResponderStandardEditActions
override func copy(sender: AnyObject?) {
UIPasteboard.generalPasteboard().string = text
}
Then in your view controller, you can add a long press gesture to your label:
let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: "handleLongPressGesture:")
label.addGestureRecognizer(gestureRecognizer)
and handle the long press with this method:
func handleLongPressGesture(recognizer: UIGestureRecognizer) {
if let recognizerView = recognizer.view,
recognizerSuperView = recognizerView.superview
{
let menuController = UIMenuController.sharedMenuController()
menuController.setTargetRect(recognizerView.frame, inView: recognizerSuperView)
menuController.setMenuVisible(true, animated:true)
recognizerView.becomeFirstResponder()
}}
NOTE: This code is taken directly from the NSHipster article, I am just including it here for SO compliance.
UILabel inherits from UIView so you can just add a long press gesture recognizer to the label. Note that you have to change isUserInteractionEnabled to true, because it defaults to false for labels.
import UIKit
class ViewController: UIViewController {
let label = UILabel()
override func viewDidLoad() {
view.addSubview(label)
label.text = "hello"
label.translatesAutoresizingMaskIntoConstraints = false
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressLabel(longPressGestureRecognizer:)))
label.addGestureRecognizer(longPressGestureRecognizer)
label.isUserInteractionEnabled = true
}
#objc private func longPressLabel (longPressGestureRecognizer: UILongPressGestureRecognizer) {
if longPressGestureRecognizer.state == .began {
print("long press began")
} else if longPressGestureRecognizer.state == .ended {
print("long press ended")
}
}
}
I've implemented a UILabel subclass that provides all of the functionality needed. Note that if you're using this with interface builder, you'll need to adjust the init methods.
/// A label that can be copied.
class CopyableLabel: UILabel
{
// MARK: - Initialisation
/// Creates a new label.
init()
{
super.init(frame: .zero)
let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressGesture(_:)))
self.addGestureRecognizer(gestureRecognizer)
self.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
// MARK: - Responder chain
override var canBecomeFirstResponder: Bool
{
return true
}
// MARK: - Actions
/// Method called when a long press is triggered.
func handleLongPressGesture(_ gestuerRecognizer: UILongPressGestureRecognizer)
{
guard let superview = self.superview else { return }
let menuController = UIMenuController.shared
menuController.setTargetRect(self.frame, in: superview)
menuController.setMenuVisible(true, animated:true)
self.becomeFirstResponder()
}
override func copy(_ sender: Any?)
{
UIPasteboard.general.string = self.text
}
}

Strange delay displaying text in input accessory view during push animation.

I'm facing problem when string in UILabel is displayed with delay in inputAccessoryView on UIViewController. I have attached gif demonstrating this problem. After pushing SecondViewController to navigation stack inputAccessoryView is missing text for short time. But I want text to be shown right away after opening screen.
Implementation demonstrating this problem is extremely simple.
class SecondViewController: UIViewController {
#IBOutlet var accessoryView: UIView!
override var inputAccessoryView: UIView {
return accessoryView
}
override func canBecomeFirstResponder() -> Bool {
return true
}
}
Does any one have solution for this problem?
I have come up with the solution which works on both iOS 8 and 9. Also it address retain cycle issue presented in iOS 9 which prevent view controller from being deallocated when use inputaccessoryview. Check github project for more details.
With lots of experimentation I have found quite hacky solution but works like a charm. Just subclass your implemantation accessory view from AccessoryView listed below.
class AccessoryView: UITextField {
override var canBecomeFirstResponder: Bool {
return true
}
override func awakeFromNib() {
super.awakeFromNib()
disableShowingKeyboard()
hideCursor()
}
}
extension AccessoryView {
private func disableShowingKeyboard() {
inputView = UIView()
}
private func hideCursor() {
tintColor = UIColor.clear
}
override func accessibilityActivate() -> Bool {
return false
}
override var isEditing: Bool {
return false
}
override func caretRect(for position: UITextPosition) -> CGRect {
return .zero
}
override func selectionRects(for range: UITextRange) -> [UITextSelectionRect] {
return []
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(UIResponder.copy(_:)) || action == #selector(UIResponder.selectAll(_:)) || action == #selector(UIResponder.paste(_:)){
return false
}
return super.canPerformAction(action, withSender: sender)
}
override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
if gestureRecognizer is UILongPressGestureRecognizer {
gestureRecognizer.isEnabled = false
}
super.addGestureRecognizer(gestureRecognizer)
}
}
extension AccessoryView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
for view in subviews {
let _point = self.convert(point, to: view)
if !view.isHidden && view.isUserInteractionEnabled && view.alpha > 0.01 && view.point(inside: _point, with: event) {
if let _view = view.hitTest(_point, with: event){
return _view
}
}
}
return super.hitTest(point, with: event)
}
}

Swift/UISwitch: how to implement a delegate/listener

In my UITableViewController I have a custom cell which contains a switcher which is the following:
import Foundation
import UIKit
class SwitchCell: UITableViewCell {
#IBOutlet weak var label : UILabel!
#IBOutlet weak var switchEmail : UISwitch!
func setEditable(canEdit:Bool) {
if (canEdit) {
self.switchEmail.enabled = true
self.label.highlighted = false
}
else {
self.switchEmail.enabled = false
self.label.highlighted = true
}
}
func configureCellWithSwitch(labelText:String, switchValue:Bool, enabled:Bool) {
var labelFrame:CGRect = self.label.frame
labelFrame.size.height = Settings.labelHeight
self.label.frame = labelFrame
self.label.text = labelText
if (switchValue) {
self.switchEmail.setOn(true, animated: true)
}
else {
self.switchEmail.setOn(false, animated: true)
}
self.setEditable(enabled)
}
}
I would like to know how to implement a listener/delegate to the switcher in order to get its value from the UITableViewController. I was able to write delegate/listeners for a cell with UITextField and UITextView implementing the methods
func controller(controller: UITableViewCell, textViewDidEndEditing: String, atIndex: Int)
and
func controller(controller: UITableViewCell, textFieldDidEndEditingWithText: String, atIndex: Int)
but I don't know what I should implement the switcher.
UISwitch has no delegate protocol. You can listen to the status as follows:
ObjC:
// somewhere in your setup:
[self.mySwitch addTarget:self action:#selector(switchChanged:) forControlEvents:UIControlEventValueChanged];
- (void)switchChanged:(UISwitch *)sender {
// Do something
BOOL value = sender.on;
}
Swift:
mySwitch.addTarget(self, action: "switchChanged:", forControlEvents: UIControlEvents.ValueChanged)
func switchChanged(mySwitch: UISwitch) {
let value = mySwitch.on
// Do something
}
Swift3 :
mySwitch.addTarget(self, action: #selector(switchChanged), for: UIControlEvents.valueChanged)
func switchChanged(mySwitch: UISwitch) {
let value = mySwitch.isOn
// Do something
}
Swift4:
mySwitch.addTarget(self, action: #selector(switchChanged), for: UIControl.Event.valueChanged)
#objc func switchChanged(mySwitch: UISwitch) {
let value = mySwitch.isOn
// Do something
}
In Swift4.0
mySwitch.addTarget(self, action: #selector(valueChange), for:UIControlEvents.valueChanged)
#objc func valueChange(mySwitch: UISwitch) {
let value = mySwitch.isOn
// Do something
print("switch value changed \(value)")
}
Another (Swift 3 or 4) method is to use didSet observer and drastically reduce code, like so-
In the class declaration declare a variable like below:
var switchFlag: Bool = false {
didSet{ //This will fire everytime the value for switchFlag is set
print(switchFlag) //do something with the switchFlag variable
}
}
Then you can have an IBAction on the UISwitch like so
#IBAction func switchChanged(_ sender: Any) {
if self.mySwitch.isOn{
switchFlag = true
}else{
switchFlag = false
}
}
Swift 3:
Using Storyboard Autolayout:
Add Reference:
#IBOutlet weak var sampleSwitch: UISwitch!
Associate method:
#IBAction func sampleSwitchValueChanged(_ sender: Any) {
if sampleSwitch.isOn {
print("ON")
}
else {
print ("OFF")
}
}
Programatic way:
Adding Target:
sampleSwitch.addTarget(self, action: #selector(ViewController.sampleSwitchValueChanged(sender:)), for: UIControlEvents.valueChanged)
The method associated with the switch:
func sampleSwitchValueChanged(sender: UISwitch!)
{
if sender.isOn {
print("switch on")
} else {
}
}
In Swift 5
switchDemo.addTarget(self, action: #selector(didTapAdvertise), for:UIControl.Event.valueChanged)
#objc func didTapAdvertise(mySwitch: UISwitch) {
let value = mySwitch.isOn
// Do something
print("switch value changed \(value)")
}
Swift 3:
#IBOutlet weak var mySwitch: UISwitch!
mySwitch.addTarget(self, action: #selector(MyClass.switchChanged(_:)), for: UIControlEvents.valueChanged)
func switchChanged(_ mySwitch: UISwitch) {
if mySwitch.isOn {
// handle on
} else {
// handle off
}
}
I have the solution in objective-c, it is the method that I use regularly:
-The Action of the switch must be in tableviewcontroller and not on the cell
-When You tap on the switch inside the action can do this to find the correct cell, then you can easily find the index or any other value that you need ...
- (IBAction)switchValueChanged:(UISwitch *)sender
{
YourCellClass *cell = (YourCellClass *)[sender findSuperViewWithClass:[YourCellClass class]];
etc....
}
the method findSuperviewWithClass is a category on UIView
- (UIView *)findSuperViewWithClass:(Class)superViewClass
{
UIView *superView = self.superview;
UIView *foundSuperView = nil;
while (nil != superView && nil == foundSuperView)
{
if ([superView isKindOfClass:superViewClass])
{
foundSuperView = superView;
} else
{
superView = superView.superview;
}
}
return foundSuperView;
}

Resources