I have 2 textfields in one view. I want to populate each using a pickerview. Currently I have succesfully made one picker with one textfield working perfectly but once I edited the code to make it working with two textfield it does not working as expected. Any help will be much appreciated.
Below is my code :
#import "TestPickerVC.h"
#interface TestPickerVC () <UITextFieldDelegate, UIPickerViewDataSource, UIPickerViewDelegate>
{
NSArray *aktivitiArray;
NSArray *penganjurArray;
}
#end
#implementation TestPickerVC
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.penganjurTextField.delegate = self;
self.aktivitiTextField.delegate = self;
aktivitiArray = #[#"apple", #"samsung", #"motorola", #"nokia"];
penganjurArray = #[#"jimbit", #"ayam", #"kambing", #"emergency"];
// self.aktivitiTextField.inputView = self.pickerView1;
// self.penganjurTextField.inputView = self.pickerView2;
self.pickerView = [[UIPickerView alloc] init];
self.pickerView.delegate = self;
self.pickerView.dataSource = self;
self.aktivitiTextField.inputView = self.pickerView;
self.penganjurTextField.inputView = self.pickerView;
self.aktivitiTextField.tag = 1;
self.penganjurTextField.tag = 2;
// if (self.aktivitiTextField.tag == 100) {
// self.aktivitiTextField.inputView = self.pickerView;
// self.pickerView.tag = 1;
// NSLog(#"pickerview tag : %d", (int)self.pickerView.tag);
// } else if (self.penganjurTextField.tag == 200) {
// self.penganjurTextField.inputView = self.pickerView;
// self.pickerView2.tag = 2;
// NSLog(#"pickerview tag : %d", (int)self.pickerView2.tag);
//
// }
}
#pragma mark - UIPickerView DataSource
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
if (self.aktivitiTextField.tag == 1) {
return aktivitiArray.count;
} else if (self.penganjurTextField.tag == 2) {
return penganjurArray.count;
}
return 1;
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
if (self.aktivitiTextField.tag == 1) {
return aktivitiArray[row];
} else if (self.penganjurTextField.tag == 2) {
return penganjurArray[row];
}
return #"";
}
#pragma mark - UIPickerView Delegate
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
NSString *selectedAktivitiRow = aktivitiArray[row];
NSString *selectedPenganjurRow = penganjurArray[row];
if (self.aktivitiTextField.tag == 1) {
self.aktivitiTextField.text = selectedAktivitiRow;
} else if (self.penganjurTextField.tag == 2) {
self.penganjurTextField.text = selectedPenganjurRow;
}
}
-(BOOL) textFieldShouldReturn:(UITextField *)textField{
[textField resignFirstResponder];
return YES;
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
#end
Create another private variable as selectedTextField.
#interface TestPickerVC () <UITextFieldDelegate, UIPickerViewDataSource, UIPickerViewDelegate>
{
NSArray *aktivitiArray;
NSArray *penganjurArray;
int selectedTextField
}
The moment you tap on text field, textfield delegate method textFieldDidBeginEditing will be called. In this method, do -
selectedTextField = textField.tag;
Now in picker delegate methods, check with this condition :
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
if (selectedTextField == 1) {
return aktivitiArray.count;
} else if (selectedTextField == 2) {
return penganjurArray.count;
}
return 1;
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
if (selectedTextField == 1) {
return aktivitiArray[row];
} else if (selectedTextField == 2) {
return penganjurArray[row];
}
return #"";
}
Do likewise for all picker delegate methods.
You didn't really explain what your issue is but looking at the code there is a pretty glaring issue. Let's look at one method as an example:
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
if (self.aktivitiTextField.tag == 1) {
return aktivitiArray.count;
} else if (self.penganjurTextField.tag == 2) {
return penganjurArray.count;
}
return 1;
}
The problem here is that the first if statement will always be true. Regardless of which text field the picker is for, self.aktivitiTextField.tag == 1 is always true. You have this issue in all of the picker view methods.
You need to check which text field is currently being used. Try the following pattern in all of the picker view methods:
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
if ([self.aktivitiTextField isFirstResponder]) {
return aktivitiArray.count;
} else if ([self.penganjurTextField isFirstResponder]) {
return penganjurArray.count;
}
return 1;
}
Instead you can use .editing for textfields and returns the respective arrays.
Ex:
if (aktivitiTextField.editing) {
return aktivitiArray.count;
} else if (penganjurArray.editing) {
return penganjurArray.count;
}
In swift 3 this is worked for me. Below is the code.
//MARK:- picker view methods
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if (textFieldName .isEqual(to: "firstDoseFld")) {
return arrFirstDose.count
}
else
{
return arrFrequency.count
}
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if (textFieldName .isEqual(to: "firstDoseFld")) {
return arrFirstDose[row]
} else {
return arrFrequency[row]
}
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if (textFieldName .isEqual(to: "firstDoseFld"))
{
selectedFirstDose = NSString()
selectedFirstDose = arrFirstDose[row] as NSString!
let cell : AddMedicationCell = ordersTable.cellForRow(at: indexPathForCell as IndexPath) as! AddMedicationCell
cell.firstDoseFld.text = selectedFirstDose as String?
} else
{
selectedFrequency = NSString()
selectedFrequency = arrFrequency[row] as NSString!
let cell : AddMedicationCell = ordersTable.cellForRow(at: indexPathForCell as IndexPath) as! AddMedicationCell
cell.frequencyField.text = selectedFrequency as String?
}
}
In textfield delegate method do like this
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool
{
print("textfield is ..\(textField)")
if textField.placeholder == "First Dose"
{
textFieldName = "firstDoseFld"
}
else if textField.placeholder == "Frequency"
{
textFieldName = "frequencyFld"
}
activeTextfield = textField
print(selectedTextField)
return true;
}
In the cellForRowAtIndexPath do like this(set delegate of custom cell textfield) and adding toolbar also here
orderCell.firstDoseFld.delegate = self
orderCell.frequencyField.delegate = self
let toolBar4 = UIToolbar()
toolBar4.barStyle = .default
toolBar4.isTranslucent = true
toolBar4.tintColor = UIColor(red: 92/255, green: 216/255, blue: 255/255, alpha: 1)
toolBar4.sizeToFit()
let doneButtonForFirstDoseFld = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(AddMedicationOrderVC.doneClickedForFirstDoseFld))
let spaceButton4 = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
// let cancelButton = UIBarButtonItem(title: "Cancel", style: .Plain, target: self, action: "cancelClick")
toolBar4.setItems([spaceButton4, doneButtonForFirstDoseFld], animated: false)
toolBar4.isUserInteractionEnabled = true
orderCell.firstDoseFld.inputView = pickerView
orderCell.firstDoseFld.inputAccessoryView = toolBar4
orderCell.firstDoseFld.tintColor = UIColor.clear
Let me know if you need more information. Delare variables based on requirement for this.
Related
I get this message:
[UITextField retain]: message sent to deallocated instance.
I understand the message BUT I do not know what "message" is sent and how to stop it from happening....
This code produces the error:
func dismissController() {
self.view.endEditing(true)
self.dismissViewControllerAnimated(false, completion: nil)
}
While the following works "fine" I am not sure why I have to delay before dismissing the controller:
func dismissController() {
self.view.endEditing(true)
let delay = 0.75 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue()) { () -> Void in
self.dismissViewControllerAnimated(false, completion: nil)
}
}
EDIT:(almost full code):
#IBOutlet weak var locationTextView: UITextField!
#IBOutlet weak var userIDTextView: UITextField!
var treeLocationArray = NSMutableArray()
var treeUserIDArray = NSMutableArray()
let pickerView = UIPickerView()
var pickerButton = UIButton()
var keyboardButton = UIButton()
var locationIsSelected = true
override func viewDidLoad() {
super.viewDidLoad()
//....
locationTextView.delegate = self
userIDTextView.delegate = self
//Get the Plist... etc
treeLocationArray = dict?.objectForKey("Location") as! NSMutableArray
treeUserIDArray = dict?.objectForKey("UserID") as! NSMutableArray
locationTextView.text = userPrefs.objectForKey("DefaultLocation") as? String
userIDTextView.text = userPrefs.objectForKey("DefaultName") as? String
locationTextView.text = userPrefs.objectForKey("DefaultLocation") as? String
/// Create a view for the Accessory View
let customView = UIView(frame: CGRectMake(0, 0, 10, 50))
customView.backgroundColor = UIColor.lightGrayColor()
/// Setup the picker button
pickerButton = UIButton(frame: CGRectMake(60 , 8, 32, 32) )
pickerButton.setImage(UIImage(named:"dropIcon"), forState: .Normal)
pickerButton.tintColor = UIColor.blueColor()
pickerButton.addTarget(self, action: #selector(UserDefaultsViewController.pickerTapped), forControlEvents: UIControlEvents.TouchUpInside)
customView.addSubview(pickerButton)
/// Setup the keyboard button
keyboardButton = UIButton(frame: CGRectMake(10 , 8, 32, 32) )
keyboardButton.setImage(UIImage(named:"keyboardIcon"), forState: .Normal)
keyboardButton.tintColor = UIColor.blueColor()
keyboardButton.addTarget(self, action: #selector(UserDefaultsViewController.keyboardTapped), forControlEvents: UIControlEvents.TouchUpInside)
customView.addSubview(keyboardButton)
locationTextView.inputAccessoryView = customView
userIDTextView.inputAccessoryView = customView
}
func textFieldDidBeginEditing(textField: UITextField) {
if textField == locationTextView {
locationIsSelected = true
}
if textField == userIDTextView {
locationIsSelected = false
}
self.pickerView.reloadAllComponents()
}
func keyboardTapped(){
if locationIsSelected {
locationTextView.resignFirstResponder()
locationTextView.inputView = nil
locationTextView.becomeFirstResponder()
}
else {
userIDTextView.resignFirstResponder()
userIDTextView.inputView = nil
userIDTextView.becomeFirstResponder()
}
}
func pickerTapped(){
if locationIsSelected {
locationTextView.resignFirstResponder()
locationTextView.inputView = nil
locationTextView.inputView = pickerView
locationTextView.becomeFirstResponder()
}
else {
userIDTextView.resignFirstResponder()
userIDTextView.inputView = nil
userIDTextView.inputView = pickerView
userIDTextView.becomeFirstResponder()
}
}
func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
var numberOfComponents = Int()
if locationIsSelected {
numberOfComponents = treeLocationArray.count
}
else {
numberOfComponents = treeUserIDArray.count
}
return numberOfComponents
}
func pickerView(pickerView: UIPickerView, numberOfRowsInSection section: Int) -> Int {
var numberOfComponents = Int()
if locationIsSelected {
numberOfComponents = treeLocationArray.count
}
else {
numberOfComponents = treeUserIDArray.count
}
return numberOfComponents
}
func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
var componetString = String()
if locationIsSelected {
componetString = (treeLocationArray[row] as? String)!
}
else {
componetString = (treeUserIDArray[row] as? String)!
}
return componetString
}
func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
if locationIsSelected {
if treeLocationArray.count >= 1 {
locationTextView.text = treeLocationArray[row] as? String
}
}
else {
if treeUserIDArray.count >= 1 {
userIDTextView.text = treeUserIDArray[row] as? String
}
}
}
This issue happens because you have call to dismissViewControllerAnimated. Due to VC life cycle all objects, that this VC holds will be deallocated. That means, that all manipulations with UI after dismiss are not memory-safe.
From the documentation:
This method looks at the current view and its subview hierarchy for
the text field that is currently the first responder. If it finds one,
it asks that text field to resign as first responder. If the force
parameter is set to true, the text field is never even asked; it is
forced to resign.
So, your VC had been deallocated while endEditing was looking thorough hierarchy, as I guess. It's the only one reason, that may cause memory problem.
Why do you need call to endEditing before dismissing? simply dismiss VC without this call. if you have logic, that depends on endEditing - separate it and call instead of endEditing. Hope this helps.
UPDATE:
Try to call endEditing in viewWillDisappear - this triggers view to resign first responder right before it will be dismissed.
After reading http://nshipster.com/uikeycommand/ i wanted to add navigation between tabs using arrow keys to my app. But i cuold not managed to receive key presses within UITabBarController. Same code block below works within UIViewController but UITabBarController.
When i tried to debug, canBecomeFirstResponder override for UITabBarController not even get called. I did not find something useful on apple s docs. Appreciate any information which makes the problem clear.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let firstResponderResult = self.becomeFirstResponder()
println("became first responder \(firstResponderResult)")
}
override func canBecomeFirstResponder() -> Bool {
return true
}
override var keyCommands: [AnyObject]? {
return [
UIKeyCommand(input: UIKeyInputRightArrow, modifierFlags: UIKeyModifierFlags.allZeros, action: "rightArrowClicked:"),
UIKeyCommand(input: UIKeyInputLeftArrow, modifierFlags: UIKeyModifierFlags.allZeros, action: "leftArrowClicked:")
]
}
func rightArrowClicked(sender: UIKeyCommand) {
let currentIndex = selectedIndex;
if (currentIndex < viewControllers!.count - 1) {
selectedIndex = currentIndex + 1;
}
}
func leftArrowClicked(sender: UIKeyCommand) {
let currentIndex = selectedIndex;
if (currentIndex > 0) {
selectedIndex = currentIndex - 1;
}
}
I think you should override canBecomeFirstResponder method of VC's inside the UITabBarController, not the tab bar controller's. This way you should be able to get key presses.
iOS13 and Mac Catalyst in Swift5:
extension UITabBarController {
open override var keyCommands: [UIKeyCommand]? {
return [
UIKeyCommand(input: UIKeyCommand.inputRightArrow, modifierFlags: .alternate, action: #selector(rightArrowClicked)),
UIKeyCommand(input: UIKeyCommand.inputLeftArrow, modifierFlags: .alternate, action: #selector(leftArrowClicked))
]
}
#objc private func rightArrowClicked(sender: UIKeyCommand) {
let currentIndex = selectedIndex;
if (currentIndex < viewControllers!.count - 1) {
selectedIndex = currentIndex + 1;
}
}
#objc private func leftArrowClicked(sender: UIKeyCommand) {
let currentIndex = selectedIndex;
if (currentIndex > 0) {
selectedIndex = currentIndex - 1;
}
}
open override var canBecomeFirstResponder: Bool {
return true
}
What is the most efficient way to find the lowest common ancestor between two UIView instances?
Short of implementing Lowest Common Ancestor, are there any UIKit APIs that can be leveraged to find it?
NSView has ancestorSharedWithView: so I suspect this might be added sooner than later to iOS.
I'm currently using this quick and dirty solution, which is inefficient if the given view isn't a sibling or direct ancestor.
- (UIView*)lyt_ancestorSharedWithView:(UIView*)aView
{
if (aView == nil) return nil;
if (self == aView) return self;
if (self == aView.superview) return self;
UIView *ancestor = [self.superview lyt_ancestorSharedWithView:aView];
if (ancestor) return ancestor;
return [self lyt_ancestorSharedWithView:aView.superview];
}
(for those implementing a similar method, the unit tests of the Lyt project might be helpful)
It's not too hard, using -isDescendantOfView:.
- (UIView *)my_ancestorSharedWithView:(UIView *)aView
{
UIView *testView = self;
while (testView && ![aView isDescendantOfView:testView])
{
testView = [testView superview];
}
return testView;
}
Swift 3:
extension UIView {
func findCommonSuperWith(_ view:UIView) -> UIView? {
var a:UIView? = self
var b:UIView? = view
var superSet = Set<UIView>()
while a != nil || b != nil {
if let aSuper = a {
if !superSet.contains(aSuper) { superSet.insert(aSuper) }
else { return aSuper }
}
if let bSuper = b {
if !superSet.contains(bSuper) { superSet.insert(bSuper) }
else { return bSuper }
}
a = a?.superview
b = b?.superview
}
return nil
}
}
A functional alternative:
Swift (assuming the use of your favourite OrderedSet)
extension UIView {
func nearestCommonSuperviewWith(other: UIView) -> UIView {
return self.viewHierarchy().intersect(other.self.viewHierarchy()).first
}
private func viewHierarchy() -> OrderedSet<UIView> {
return Set(UIView.hierarchyFor(self, accumulator: []))
}
static private func hierarchyFor(view: UIView?, accumulator: [UIView]) -> [UIView] {
guard let view = view else {
return accumulator
}
return UIView.hierarchyFor(view.superview, accumulator: accumulator + [view])
}
}
Objective-C (implemented as a category on UIView, assuming the existence of a firstObjectCommonWithArray method)
+ (NSArray *)hierarchyForView:(UIView *)view accumulator:(NSArray *)accumulator
{
if (!view) {
return accumulator;
}
else {
return [self.class hierarchyForView:view.superview accumulator:[accumulator arrayByAddingObject:view]];
}
}
- (NSArray *)viewHierarchy
{
return [self.class hierarchyForView:self accumulator:#[]];
}
- (UIView *)nearestCommonSuperviewWithOtherView:(UIView *)otherView
{
return [[self viewHierarchy] firstObjectCommonWithArray:[otherView viewHierarchy]];
}
Here's a little shorter version, as a category on UIView:
- (UIView *)nr_commonSuperview:(UIView *)otherView
{
NSMutableSet *views = [NSMutableSet set];
UIView *view = self;
do {
if (view != nil) {
if ([views member:view])
return view;
[views addObject:view];
view = view.superview;
}
if (otherView != nil) {
if ([views member:otherView])
return otherView;
[views addObject:otherView];
otherView = otherView.superview;
}
} while (view || otherView);
return nil;
}
Your implementation only check two view level in one iteration.
Here is mine:
+ (UIView *)commonSuperviewWith:(UIView *)view1 anotherView:(UIView *)view2 {
NSParameterAssert(view1);
NSParameterAssert(view2);
if (view1 == view2) return view1.superview;
// They are in diffrent window, so they wont have a common ancestor.
if (view1.window != view2.window) return nil;
// As we don’t know which view has a heigher level in view hierarchy,
// We will add these view and their superview to an array.
NSMutableArray *mergedViewHierarchy = [#[ view1, view2 ] mutableCopy];
UIView *commonSuperview = nil;
// Loop until all superviews are included in this array or find a view’s superview in this array.
NSInteger checkIndex = 0;
UIView *checkingView = nil;
while (checkIndex < mergedViewHierarchy.count && !commonSuperview) {
checkingView = mergedViewHierarchy[checkIndex++];
UIView *superview = checkingView.superview;
if ([mergedViewHierarchy containsObject:superview]) {
commonSuperview = superview;
}
else if (checkingView.superview) {
[mergedViewHierarchy addObject:superview];
}
}
return commonSuperview;
}
Mine is a bit longer and without using UIKit isDescendant function.
Method 1: With a method of finding LCA in trees. Time complexity:O(N), Space complexity: (1)
func findCommonSuper(_ view1:inout UIView, _ view2:inout UIView) -> UIView? {
var level1 = findLevel(view1)
var level2 = findLevel(view2)
if level1 > level2 {
var dif = level1-level2
while dif > 0 {
view1 = view1.superview!
dif -= 1
}
} else if level1 < level2 {
var dif = level2-level1
while dif > 0 {
view2 = view2.superview!
dif -= 1
}
}
while view1 != view2 {
if view1.superview == nil || view2.superview == nil {
return nil
}
view1 = view1.superview!
view2 = view2.superview!
}
if view1 == view2 {
return view1
}
return nil
}
func findLevel(_ view:UIView) -> Int {
var level = 0
var view = view
while view.superview != nil {
view = view.superview!
level += 1
}
return level
}
Method 2: Inserting one view's ancestors to set and then iterating second ones ancestors. Time complexity: O(N), Space complexity: O(N)
func findCommonSuper2(_ view1:UIView, _ view2:UIView) -> UIView? {
var set = Set<UIView>()
var view = view1
while true {
set.insert(view)
if view.superview != nil {
view = view.superview!
} else {
break
}
}
view = view2
while true {
if set.contains(view) {
return view
}
if view.superview != nil {
view = view.superview!
} else {
break
}
}
return nil
}
Swift 2.0:
let view1: UIView!
let view2: UIView!
let sharedSuperView = view1.getSharedSuperview(withOtherView: view2)
/**
* A set of helpful methods to find shared superview for two given views
*
* #author Alexander Volkov
* #version 1.0
*/
extension UIView {
/**
Get nearest shared superview for given and otherView
- parameter otherView: the other view
*/
func getSharedSuperview(withOtherView otherView: UIView) {
(self.getViewHierarchy() as NSArray).firstObjectCommonWithArray(otherView.getViewHierarchy())
}
/**
Get array of views in given view hierarchy
- parameter view: the view whose hierarchy need to get
- parameter accumulator: the array to accumulate views in
- returns: the list of views from given up to the top most view
*/
class func getHierarchyForView(view: UIView?, var accumulator: [UIView]) -> [UIView] {
if let superview = view?.superview {
accumulator.append(view!)
return UIView.getHierarchyForView(superview, accumulator: accumulator)
}
return accumulator
}
/**
Get array of views in the hierarchy of the current view
- returns: the list of views from cuurent up to the top most view
*/
func getViewHierarchy() -> [UIView] {
return UIView.getHierarchyForView(self, accumulator: [])
}
}
Swift 5 version of Carl Lindberg's solution:
func nearestCommonSuperviewWith(other: UIView) -> UIView? {
var nearestAncestor: UIView? = self
while let testView = nearestAncestor, !other.isDescendant(of: testView) {
nearestAncestor = testView.superview
}
return nearestAncestor
}
I am making a registration alertview that has a UITextField in it where the user can enter their registration number. everything is pretty much their, however I would like to remove the copy paste function from the textfield programmatically since their is no InterfaceBuilder version of the textfield I have no idea how to do this..
here Is my UIalertview thus far...
- (void)pleaseRegisterDevice {
UIAlertView *myAlertView = [[UIAlertView alloc] initWithTitle:#"Please Register Device!" message:#"this gets covered" delegate:self cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
regTextField = [[UITextField alloc] initWithFrame:CGRectMake(12.0, 45.0, 260.0, 25.0)];
[regTextField setBackgroundColor:[UIColor whiteColor]];
regTextField.textAlignment = UITextAlignmentCenter;
[myAlertView addSubview:regTextField];
[myAlertView show];
[myAlertView release];
}
This post has many nice solutions: How disable Copy, Cut, Select, Select All in UITextView
My favourite is to override canPerformAction:withSender::
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
if (action == #selector(paste:))
return NO;
return [super canPerformAction:action withSender:sender];
}
Storyboard users may want to look at this solution, as long as you are ok with subclassing.
I don't think that there is an easy way to achieve this through extensions or protocols.
Swift 3.1
import UIKit
#IBDesignable
class CustomTextField: UITextField {
#IBInspectable var isPasteEnabled: Bool = true
#IBInspectable var isSelectEnabled: Bool = true
#IBInspectable var isSelectAllEnabled: Bool = true
#IBInspectable var isCopyEnabled: Bool = true
#IBInspectable var isCutEnabled: Bool = true
#IBInspectable var isDeleteEnabled: Bool = true
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)
}
}
}
Gist link
For iOS8.0+, Xcode 6.0.1, ARC enabled
Hoping to save a beginner, like myself, some time implementing this...
To implement disabling copy/paste/cut/etc. you must subclass UITextField and override...
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
To do this...
Create a new class that is a subclass of UITextField (i.e. a new .h and .m files to be included within your app folder). So File->New->"Cocoa Touch Class"->Next->"PasteOnlyUITextField" (for example), subclass of "UITextField"->Next->Create.
Once the .h and .m files are created for our new subclass of UITextField called "PasteOnlyUITextField"...
PasteOnlyUITextField.h
#import <UIKit/UIKit.h>
#interface PasteOnlyUITextField : UITextField
#end
PasteOnlyUITextField.m
#import "PasteOnlyUITextField.h"
#implementation PasteOnlyUITextField
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
if (action == #selector(paste:))
{
return true;
}
return false;
}
#end
Now make sure you import PasteOnlyUITextField.h where you are going to use it, e.g. YourUIViewController.h file...
#import "PasteOnlyUITextField.h"
Now you must use the subclass, either progrommatically or with identity inspector
PasteOnlyUITextField *pasteOnlyUITextField = [[PasteOnlyUITextField alloc] init...];
or...
Select the UITextField and go to the identity inspector, select its class.
You can change the logic associated with the menu options as you see fit...
Hope this helps! Thanks to all the original contributors.
I have found a way with swift using extension and associatedObject without subclassing.
I use a property readonly to disable paste/cut but this sample can be adapted.
Swift 3 updated as of 27/11/2016
var key: Void?
class UITextFieldAdditions: NSObject {
var readonly: Bool = false
}
extension UITextField {
var readonly: Bool {
get {
return self.getAdditions().readonly
} set {
self.getAdditions().readonly = newValue
}
}
private func getAdditions() -> UITextFieldAdditions {
var additions = objc_getAssociatedObject(self, &key) as? UITextFieldAdditions
if additions == nil {
additions = UITextFieldAdditions()
objc_setAssociatedObject(self, &key, additions!, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
return additions!
}
open override func target(forAction action: Selector, withSender sender: Any?) -> Any? {
if ((action == #selector(UIResponderStandardEditActions.paste(_:)) || (action == #selector(UIResponderStandardEditActions.cut(_:)))) && self.readonly) {
return nil
}
return super.target(forAction: action, withSender: sender)
}
}
Other Swift (2.2)
import UIKit
var key: Void?
class UITextFieldAdditions: NSObject {
var readonly: Bool = false
}
extension UITextField {
var readonly: Bool {
get {
return self.getAdditions().readonly
}
set {
self.getAdditions().readonly = newValue
}
}
private func getAdditions() -> UITextFieldAdditions {
var additions = objc_getAssociatedObject(self, &key) as? UITextFieldAdditions
if additions == nil {
additions = UITextFieldAdditions()
objc_setAssociatedObject(self, &key, additions!, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
}
return additions!
}
public override func targetForAction(action: Selector, withSender sender: AnyObject?) -> AnyObject? {
if ((action == Selector("paste:") || (action == Selector("cut:"))) && self.readonly) {
return nil
}
return super.targetForAction(action, withSender: sender)
}
}
Implement this Method in ViewController.m This Method will help you to disable Options on UITextField.
It Includes paste, select, selectAll and copy option on your Corresponding UITextField.
This method is very useful in case of UITextField when you want to take this for Password or DateOfBirth or whatever you want.
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
if ((_TextField1 isFirstResponder] || [_TextFied2 isFirstResponder]) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[[UIMenuController sharedMenuController] setMenuVisible:NO animated:NO];
}];
}
return [super canPerformAction:action withSender:sender];
}
In Swift, If you want your text field to disable all UIResponderStandardEditActions (cut, copy, paste, look up, share, select), use this in UITextFieldDelegate.
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
textField.isUserInteractionEnabled = false
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
textField.isUserInteractionEnabled = true
}
In iOS 9 we can hide the copy paste bar from keyboard
-(void) customMethod{
yourTextField.inputAssistantItem.leadingBarButtonGroups = #[];
yourTextField.inputAssistantItem.trailingBarButtonGroups = #[];
}
Swift 5 solution:
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(UIResponderStandardEditActions.copy(_:)) || action == #selector(UIResponderStandardEditActions.paste(_:)) {
return false
}
return true
}
Small update of this answer for iOS 10 and earlier (Swift 3):
open override func target(forAction action: Selector, withSender sender: Any?) -> Any? {
guard isReadonly else {
return super.target(forAction: action, withSender: sender)
}
if #available(iOS 10, *) {
if action == #selector(UIResponderStandardEditActions.paste(_:)) {
return nil
}
} else {
if action == #selector(paste(_:)) {
return nil
}
}
return super.target(forAction: action, withSender: sender)
}
Try this in your viewController
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[[UIMenuController sharedMenuController] setMenuVisible:NO animated:NO];
}];
return [super canPerformAction:action withSender:sender];
}
Disable all actions by UITextField subclass.
import UIKit
class CustomTextField: UITextField {
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
you can extension textview or textfield in swift, like this:
extension UITextView {
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
If disabled text selection works for you, try this.
class NoMoreSelectionTextField: UITextField {
override func caretRect(for position: UITextPosition) -> CGRect {
return CGRect.zero
}
override var selectedTextRange: UITextRange? {
get { return nil }
set { return }
}
}
Overriding targetForAction:withSender is best IMHO:
- (id)targetForAction:(SEL)action withSender:(id)sender
{
if (action == #selector(paste:)) {
return nil;
}
return [super targetForAction:action withSender:sender];
}
Swift 3.0 version
class NoMenuTextField: UITextField {
override func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool {
if (action == #selector(NSObject.paste(_:))) {
return false
}
return super.canPerformAction(action, withSender: sender)
}
}
use for iOS 7 or later
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[[UIMenuController sharedMenuController] setMenuVisible:NO animated:NO];
}];
return [super canPerformAction:action withSender:sender];
}
Just set userInteractionEnabled = NO;
I know about UITableview: How to Disable Selection for Some Rows but Not Others and cell.selectionStyle = UITableViewCellSelectionStyleNone, but how do I make a cell (or any UIView for that matter) appear disabled (grayed-out) like below?
You can just disable the cell's text fields to gray them out:
Swift 4.x
cell!.isUserInteractionEnabled = false
cell!.textLabel!.isEnabled = false
cell!.detailTextLabel!.isEnabled = false
A Swift extension that works well in the context I'm using it; your mileage may vary.
Swift 2.x
extension UITableViewCell {
func enable(on: Bool) {
for view in contentView.subviews as! [UIView] {
view.userInteractionEnabled = on
view.alpha = on ? 1 : 0.5
}
}
}
Swift 3:
extension UITableViewCell {
func enable(on: Bool) {
for view in contentView.subviews {
view.isUserInteractionEnabled = on
view.alpha = on ? 1 : 0.5
}
}
}
Now it's just a matter of calling myCell.enable(truthValue).
Thanks to #Ajay Sharma, I figured out how to make a UITableViewCell appear disabled:
// Mac's native DigitalColor Meter reads exactly {R:143, G:143, B:143}.
cell.textLabel.alpha = 0.439216f; // (1 - alpha) * 255 = 143
aSwitch.enabled = NO; // or [(UISwitch *)cell.accessoryView setEnabled:NO];
And then, to actually disable the cell:
cell.userInteractionEnabled = NO;
Try using a small trick:
Just set the alpha of the cell. Put some condition as your own requirements & set the alpha.
cell.alpha=0.2;
If it does't work,the way you like it to be then, Use second trick,
Just take an image of the cell size having gray background with Transparent Background, just add that image in image over the cell content.
Like this:
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
if(indexPath.row==0)
{
cell.userInteractionEnabled=FALSE;
UIImageView *img=[[UIImageView alloc]init];
img.frame=CGRectMake(0, 0, 320, 70);
img.image=[UIImage imageNamed:#"DisableImage.png"];
img.backgroundColor=[UIColor clearColor];
[cell.contentView addSubview:img];
[img release];
}
else {
//Your usual code for cell interaction.
}
return cell;
}
Although I am not not sure about the way,but this will surely fulfill your requirement.This will give a kind of illusion in user's mind that the cell is Disable.
Just try using this solution.Hope that will solve your problem.
Swift 4.X
Nice Extension from Kevin Owens,
I am correcting the behaviour of cell.
extension UITableViewCell {
func enable(on: Bool) {
self.isUserInteractionEnabled = on
for view in contentView.subviews {
self.isUserInteractionEnabled = on
view.alpha = on ? 1 : 0.5
}
}
}
How to call this:-
cell.enable(on: switch.isOn)
Great extension from Kevin Owens, this is my correction to working with Swift 2.x:
extension UITableViewCell {
func enable(on: Bool) {
self.userInteractionEnabled = on
for view in contentView.subviews {
view.userInteractionEnabled = on
view.alpha = on ? 1 : 0.5
}
}
}
Swift 3:
extension UITableViewCell {
func enable(on: Bool) {
self.isUserInteractionEnabled = on
for view in contentView.subviews {
view.isUserInteractionEnabled = on
view.alpha = on ? 1 : 0.5
}
}
}
I have created following extension to Enable/Disable UITableViewCell, it is very convenient to use it.
Create UITableViewCell Extension with "UITableViewCell+Ext.h" contain following in it.
#interface UITableViewCell (Ext)
- (void)enableCell:(BOOL)enabled withText:(BOOL)text;
- (void)enableCell:(BOOL)enabled withText:(BOOL)text withDisclosureIndicator:(BOOL)disclosureIndicator;
- (void)disclosureIndicator:(BOOL)disclosureIndicator;
#end
"UITableViewCell+Ext.m" contain following in it.
#implementation UITableViewCell (Ext)
- (UITableView *)uiTableView {
if ([[UIDevice currentDevice] systemVersionIsGreaterThanOrEqualTo:#"7.0"]) {
return (UITableView *)self.superview.superview;
}
else {
return (UITableView *)self.superview;
}
}
- (void)enableCell:(BOOL)enabled withText:(BOOL)text {
if (enabled) {
self.userInteractionEnabled = YES;
if (text) {
self.textLabel.alpha = 1.0f;
self.alpha = 1.0f;
self.detailTextLabel.hidden = NO;
}
}
else {
self.userInteractionEnabled = NO;
if (text) {
self.textLabel.alpha = 0.5f;
self.alpha = 0.5f;
self.detailTextLabel.hidden = YES;
}
}
}
- (void)enableCell:(BOOL)enabled withText:(BOOL)text withDisclosureIndicator:(BOOL)disclosureIndicator {
if (enabled) {
self.userInteractionEnabled = YES;
if (text) {
self.textLabel.alpha = 1.0f;
self.alpha = 1.0f;
self.detailTextLabel.hidden = NO;
}
self.accessoryType = disclosureIndicator ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone;
}
else {
self.userInteractionEnabled = NO;
if (text) {
self.textLabel.alpha = 0.5f;
self.alpha = 0.5f;
self.detailTextLabel.hidden = YES;
}
self.accessoryType = UITableViewCellAccessoryNone;
}
}
- (void)disclosureIndicator:(BOOL)disclosureIndicator {
if (disclosureIndicator) {
self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
else {
self.accessoryType = UITableViewCellAccessoryNone;
}
}
#end
How to Disable Cell:
[cell enableCell:NO withText:NO];
[cell enableCell:NO withText:YES withDisclosureIndicator:YES];
How to Enable Cell:
[cell enableCell:YES withText:NO];
[cell enableCell:YES withText:YES withDisclosureIndicator:YES];
Hope it helps you.
for swift
cell.isUserInteractionEnabled = false
Swift 5 version
class CustomCell: UITableViewCell {
private func isEnabled(_ enabled: Bool) {
isUserInteractionEnabled = enabled
subviews.forEach { subview in
subview.isUserInteractionEnabled = enabled
subview.alpha = enabled ? 1 : 0.5
}
}
}