Intercept Objective-C delegate messages within a subclass - ios

I have a subclass of UIScrollView in which I need to internally respond to scrolling behaviour. However, the viewcontroller will still need to listen to scrolling delegate callbacks, so I can't outright steal the delegate within my component.
Is there a way to keep the property named "delegate" and just listen to messages sent along it, or else somehow internally hijack the delegate property and forward messages outward after running some code?

To avoid overriding all of the delegate methods manually, you can use message forwarding. I just implemented the same thing using an intermediate proxy class as follows:
MessageInterceptor.h
#interface MessageInterceptor : NSObject {
id receiver;
id middleMan;
}
#property (nonatomic, assign) id receiver;
#property (nonatomic, assign) id middleMan;
#end
MessageInterceptor.m
#implementation MessageInterceptor
#synthesize receiver;
#synthesize middleMan;
- (id)forwardingTargetForSelector:(SEL)aSelector {
if ([middleMan respondsToSelector:aSelector]) { return middleMan; }
if ([receiver respondsToSelector:aSelector]) { return receiver; }
return [super forwardingTargetForSelector:aSelector];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
if ([middleMan respondsToSelector:aSelector]) { return YES; }
if ([receiver respondsToSelector:aSelector]) { return YES; }
return [super respondsToSelector:aSelector];
}
#end
MyScrollView.h
#import "MessageInterceptor.h"
#interface MyScrollView : UIScrollView {
MessageInterceptor * delegate_interceptor;
//...
}
//...
#end
MyScrollView.m (Edited, with thanks to jhabbott):
#implementation MyScrollView
- (id)delegate { return delegate_interceptor.receiver; }
- (void)setDelegate:(id)newDelegate {
[super setDelegate:nil];
[delegate_interceptor setReceiver:newDelegate];
[super setDelegate:(id)delegate_interceptor];
}
- (id)init* {
//...
delegate_interceptor = [[MessageInterceptor alloc] init];
[delegate_interceptor setMiddleMan:self];
[super setDelegate:(id)delegate_interceptor];
//...
}
- (void)dealloc {
//...
[delegate_interceptor release];
//...
}
// delegate method override:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// 1. your custom code goes here
// 2. forward to the delegate as usual
if ([self.delegate respondsToSelector:#selector(scrollViewDidScroll:)]) {
[self.delegate scrollViewDidScroll:scrollView];
}
}
#end
With this approach, the MessageInterceptor object will automatically forward all delegate messages to the regular delegate object, except for the ones that you override in your custom subclass.

The post from e.James gave an excellent solution for most views. But for keyboard dependent views like UITextField and UITextView, it often results in a situation of infinite loop. To get rid of it, I fixed it with some additional code what checks whether the selector is contained in specific protocol(s) or not.
WZProtocolInterceptor.h
#import <Foundation/Foundation.h>
#interface WZProtocolInterceptor : NSObject
#property (nonatomic, readonly, copy) NSArray * interceptedProtocols;
#property (nonatomic, weak) id receiver;
#property (nonatomic, weak) id middleMan;
- (instancetype)initWithInterceptedProtocol:(Protocol *)interceptedProtocol;
- (instancetype)initWithInterceptedProtocols:(Protocol *)firstInterceptedProtocol, ... NS_REQUIRES_NIL_TERMINATION;
- (instancetype)initWithArrayOfInterceptedProtocols:(NSArray *)arrayOfInterceptedProtocols;
#end
WZProtocolInterceptor.m
#import <objc/runtime.h>
#import "WZProtocolInterceptor.h"
static inline BOOL selector_belongsToProtocol(SEL selector, Protocol * protocol);
#implementation WZProtocolInterceptor
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if ([self.middleMan respondsToSelector:aSelector] &&
[self isSelectorContainedInInterceptedProtocols:aSelector])
return self.middleMan;
if ([self.receiver respondsToSelector:aSelector])
return self.receiver;
return [super forwardingTargetForSelector:aSelector];
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
if ([self.middleMan respondsToSelector:aSelector] &&
[self isSelectorContainedInInterceptedProtocols:aSelector])
return YES;
if ([self.receiver respondsToSelector:aSelector])
return YES;
return [super respondsToSelector:aSelector];
}
- (instancetype)initWithInterceptedProtocol:(Protocol *)interceptedProtocol
{
self = [super init];
if (self) {
_interceptedProtocols = #[interceptedProtocol];
}
return self;
}
- (instancetype)initWithInterceptedProtocols:(Protocol *)firstInterceptedProtocol, ...;
{
self = [super init];
if (self) {
NSMutableArray * mutableProtocols = [NSMutableArray array];
Protocol * eachInterceptedProtocol;
va_list argumentList;
if (firstInterceptedProtocol)
{
[mutableProtocols addObject:firstInterceptedProtocol];
va_start(argumentList, firstInterceptedProtocol);
while ((eachInterceptedProtocol = va_arg(argumentList, id))) {
[mutableProtocols addObject:eachInterceptedProtocol];
}
va_end(argumentList);
}
_interceptedProtocols = [mutableProtocols copy];
}
return self;
}
- (instancetype)initWithArrayOfInterceptedProtocols:(NSArray *)arrayOfInterceptedProtocols
{
self = [super init];
if (self) {
_interceptedProtocols = [arrayOfInterceptedProtocols copy];
}
return self;
}
- (void)dealloc
{
_interceptedProtocols = nil;
}
- (BOOL)isSelectorContainedInInterceptedProtocols:(SEL)aSelector
{
__block BOOL isSelectorContainedInInterceptedProtocols = NO;
[self.interceptedProtocols enumerateObjectsUsingBlock:^(Protocol * protocol, NSUInteger idx, BOOL *stop) {
isSelectorContainedInInterceptedProtocols = selector_belongsToProtocol(aSelector, protocol);
* stop = isSelectorContainedInInterceptedProtocols;
}];
return isSelectorContainedInInterceptedProtocols;
}
#end
BOOL selector_belongsToProtocol(SEL selector, Protocol * protocol)
{
// Reference: https://gist.github.com/numist/3838169
for (int optionbits = 0; optionbits < (1 << 2); optionbits++) {
BOOL required = optionbits & 1;
BOOL instance = !(optionbits & (1 << 1));
struct objc_method_description hasMethod = protocol_getMethodDescription(protocol, selector, required, instance);
if (hasMethod.name || hasMethod.types) {
return YES;
}
}
return NO;
}
And here is the Swift 2 version:
//
// NSProtocolInterpreter.swift
// Nest
//
// Created by Manfred Lau on 11/28/14.
// Copyright (c) 2014 WeZZard. All rights reserved.
//
import Foundation
/**
`NSProtocolInterceptor` is a proxy which intercepts messages to the middle man
which originally intended to send to the receiver.
- Discussion: `NSProtocolInterceptor` is a class cluster which dynamically
subclasses itself to conform to the intercepted protocols at the runtime.
*/
public final class NSProtocolInterceptor: NSObject {
/// Returns the intercepted protocols
public var interceptedProtocols: [Protocol] { return _interceptedProtocols }
private var _interceptedProtocols: [Protocol] = []
/// The receiver receives messages
public weak var receiver: NSObjectProtocol?
/// The middle man intercepts messages
public weak var middleMan: NSObjectProtocol?
private func doesSelectorBelongToAnyInterceptedProtocol(
aSelector: Selector) -> Bool
{
for aProtocol in _interceptedProtocols
where sel_belongsToProtocol(aSelector, aProtocol)
{
return true
}
return false
}
/// Returns the object to which unrecognized messages should first be
/// directed.
public override func forwardingTargetForSelector(aSelector: Selector)
-> AnyObject?
{
if middleMan?.respondsToSelector(aSelector) == true &&
doesSelectorBelongToAnyInterceptedProtocol(aSelector)
{
return middleMan
}
if receiver?.respondsToSelector(aSelector) == true {
return receiver
}
return super.forwardingTargetForSelector(aSelector)
}
/// Returns a Boolean value that indicates whether the receiver implements
/// or inherits a method that can respond to a specified message.
public override func respondsToSelector(aSelector: Selector) -> Bool {
if middleMan?.respondsToSelector(aSelector) == true &&
doesSelectorBelongToAnyInterceptedProtocol(aSelector)
{
return true
}
if receiver?.respondsToSelector(aSelector) == true {
return true
}
return super.respondsToSelector(aSelector)
}
/**
Create a protocol interceptor which intercepts a single Objecitve-C
protocol.
- Parameter protocols: An Objective-C protocol, such as
UITableViewDelegate.self.
*/
public class func forProtocol(aProtocol: Protocol)
-> NSProtocolInterceptor
{
return forProtocols([aProtocol])
}
/**
Create a protocol interceptor which intercepts a variable-length sort of
Objecitve-C protocols.
- Parameter protocols: A variable length sort of Objective-C protocol,
such as UITableViewDelegate.self.
*/
public class func forProtocols(protocols: Protocol ...)
-> NSProtocolInterceptor
{
return forProtocols(protocols)
}
/**
Create a protocol interceptor which intercepts an array of Objecitve-C
protocols.
- Parameter protocols: An array of Objective-C protocols, such as
[UITableViewDelegate.self].
*/
public class func forProtocols(protocols: [Protocol])
-> NSProtocolInterceptor
{
let protocolNames = protocols.map { NSStringFromProtocol($0) }
let sortedProtocolNames = protocolNames.sort()
let concatenatedName = sortedProtocolNames.joinWithSeparator(",")
let theConcreteClass = concreteClassWithProtocols(protocols,
concatenatedName: concatenatedName,
salt: nil)
let protocolInterceptor = theConcreteClass.init()
as! NSProtocolInterceptor
protocolInterceptor._interceptedProtocols = protocols
return protocolInterceptor
}
/**
Return a subclass of `NSProtocolInterceptor` which conforms to specified
protocols.
- Parameter protocols: An array of Objective-C protocols. The
subclass returned from this function will conform to these protocols.
- Parameter concatenatedName: A string which came from concatenating
names of `protocols`.
- Parameter salt: A UInt number appended to the class name
which used for distinguishing the class name itself from the duplicated.
- Discussion: The return value type of this function can only be
`NSObject.Type`, because if you return with `NSProtocolInterceptor.Type`,
you can only init the returned class to be a `NSProtocolInterceptor` but not
its subclass.
*/
private class func concreteClassWithProtocols(protocols: [Protocol],
concatenatedName: String,
salt: UInt?)
-> NSObject.Type
{
let className: String = {
let basicClassName = "_" +
NSStringFromClass(NSProtocolInterceptor.self) +
"_" + concatenatedName
if let salt = salt { return basicClassName + "_\(salt)" }
else { return basicClassName }
}()
let nextSalt = salt.map {$0 + 1}
if let theClass = NSClassFromString(className) {
switch theClass {
case let anInterceptorClass as NSProtocolInterceptor.Type:
let isClassConformsToAllProtocols: Bool = {
// Check if the found class conforms to the protocols
for eachProtocol in protocols
where !class_conformsToProtocol(anInterceptorClass,
eachProtocol)
{
return false
}
return true
}()
if isClassConformsToAllProtocols {
return anInterceptorClass
} else {
return concreteClassWithProtocols(protocols,
concatenatedName: concatenatedName,
salt: nextSalt)
}
default:
return concreteClassWithProtocols(protocols,
concatenatedName: concatenatedName,
salt: nextSalt)
}
} else {
let subclass = objc_allocateClassPair(NSProtocolInterceptor.self,
className,
0)
as! NSObject.Type
for eachProtocol in protocols {
class_addProtocol(subclass, eachProtocol)
}
objc_registerClassPair(subclass)
return subclass
}
}
}
/**
Returns true when the given selector belongs to the given protocol.
*/
public func sel_belongsToProtocol(aSelector: Selector,
_ aProtocol: Protocol) -> Bool
{
for optionBits: UInt in 0..<(1 << 2) {
let isRequired = optionBits & 1 != 0
let isInstance = !(optionBits & (1 << 1) != 0)
let methodDescription = protocol_getMethodDescription(aProtocol,
aSelector, isRequired, isInstance)
if !objc_method_description_isEmpty(methodDescription)
{
return true
}
}
return false
}
public func objc_method_description_isEmpty(
var methodDescription: objc_method_description)
-> Bool
{
let ptr = withUnsafePointer(&methodDescription) { UnsafePointer<Int8>($0) }
for offset in 0..<sizeof(objc_method_description) {
if ptr[offset] != 0 {
return false
}
}
return true
}

Actually, this worked for me:
#implementation MySubclass {
id _actualDelegate;
}
// There is no need to set the value of _actualDelegate in an init* method
- (void)setDelegate:(id)newDelegate {
[super setDelegate:nil];
_actualDelegate = newDelegate;
[super setDelegate:(id)self];
}
- (id)delegate {
return self;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
if ([_actualDelegate respondsToSelector:aSelector]) { return _actualDelegate; }
return [super forwardingTargetForSelector:aSelector];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [super respondsToSelector:aSelector] || [_actualDelegate respondsToSelector:aSelector];
}
#end
...making the subclass to be the message interceptor in the awesome answer given by e.James.

Yes, but you'll have to override every delegate method in the docs. Basically, make a second delegate property and implement the delegate protocol. When your delegate methods are called, take care of your business and then call the same method on your second delegate from the delegate method that was just run. E.g.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// Do stuff here
if ([self.delegate2 respondsToSelector:#selector(scrollViewDidScroll:)]) {
[self.delegate2 scrollViewDidScroll:scrollView];
}
}

Related

UITextFieldDelegate textFieldShouldReturn with ReactiveCocoa

I am trying to implement UITextFieldDelegate textFieldShouldReturn handling with ReactiveCocoa. Unfortunately the subscribeNext block is run when I subscribe for the signal.
The implementation using delegation would be:
- (void)viewDidLoad
{
...
self.myTextField.delegate = self;
}
...
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
if (textField == self.myTextField) {
NSLog(#"Let's go!");
}
return YES;
}
In ReactiveCocoa I have added a category for UITextField in a similar fashion like UITextView+RACSignalSupport.
#implementation UITextField (RACKeyboardSupport)
static void RACUseDelegateProxy(UITextField *self)
{
if (self.delegate == self.rac_delegateProxy) return;
self.rac_delegateProxy.rac_proxiedDelegate = self.delegate;
self.delegate = (id)self.rac_delegateProxy;
}
- (RACDelegateProxy *)rac_delegateProxy
{
RACDelegateProxy *proxy = objc_getAssociatedObject(self, _cmd);
if (proxy == nil) {
proxy = [[RACDelegateProxy alloc] initWithProtocol:#protocol(UITextFieldDelegate)];
objc_setAssociatedObject(self, _cmd, proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return proxy;
}
- (RACSignal *)rac_keyboardReturnSignal
{
#weakify(self);
RACSignal *signal = [[[[RACSignal
defer:^{
#strongify(self);
return [RACSignal return:RACTuplePack(self)];
}]
concat:[self.rac_delegateProxy signalForSelector:#selector(textFieldShouldReturn:)]]
takeUntil:self.rac_willDeallocSignal]
setNameWithFormat:#"%# -rac_keyboardReturnSignal", [self rac_description]];
RACUseDelegateProxy(self);
return signal;
}
#end
Here subscribeNext block is executed even if the Return Key was never pressed:
- (void)viewDidLoad
{
...
[self.myTextField.rac_keyboardReturnSignal subscribeNext:^(id x) {
Log(#"Let's go with RAC!");
}];
}
I have to use skip:1 to avoid that problem:
- (void)viewDidLoad
{
...
[[self.myTextField.rac_keyboardReturnSignal skip:1] subscribeNext:^(id x) {
Log(#"Let's go with RAC!");
}];
}
Any idea why this happens?
Solution:
- (RACSignal *)rac_keyboardReturnSignal
{
RACSignal *signal = [[[self.rac_delegateProxy
signalForSelector:#selector(textFieldShouldReturn:)]
takeUntil:self.rac_willDeallocSignal]
setNameWithFormat:#"%# -rac_keyboardReturnSignal", [self rac_description]];
RACUseDelegateProxy(self);
return signal;
}
You are returning a signal that immediately returns a value in your defer block, then concat-ing new values onto the stream when textFieldShouldReturn is invoked.
The code in UITextView+RACSignalSupport.m is calling reduceEach in order to return a string value that is extracted from the UITextView instance. The defer is used to merely have an initial value generated upon subscription.
Basically, I don't think you want the defer at all for your use case.

Writing a Custom Setter for my Variable - Swift

I am trying to run a Setter on a property when it changes, that will then also affect the logic in that class:
Here is how it used to look in Objective - C:
#property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
Set Method for fetchedResultsController:
- (void)setFetchedResultsController:(NSFetchedResultsController *)newFetchResultsContrller
{
NSFetchedResultsController *oldfetchResultsController = _fetchedResultsController;
if (newFetchResultsContrller != oldfetchResultsController) {
_fetchedResultsController = newFetchResultsContrller;
newFetchResultsContrller.delegate = self;
if ((!self.title || [self.title isEqualToString:oldfetchResultsController.fetchRequest.entity.name]) && (!self.navigationController || !self.navigationItem.title)) {
self.title = newFetchResultsContrller.fetchRequest.entity.name;
}
if (newFetchResultsContrller) {
[self performFetch];
} else {
[self.tableView reloadData];
}
}
}
Here is my attempt in Swift. However I cannot get the newFetchResultsController, thus cannot complete rest. Is this the right way of setting a property in swift ? How can I do the same principle as above shown in Objective C in Swift?
var fetchedResultsController:NSFetchedResultsController! {
willSet {
println("Set fetchedResultsController Called")
self.fetchedResultsController.delegate = self
performFetch()
}
}
UPD:
Today with Swift 3.0 your logic can be reimplemented, you may have single plain stored property with only didSet value observer specified like this:
var fetchedResultsController:NSFetchedResultsController? {
didSet(oldfetchResultsController) {
// If value BEFORE it was set is not identical to value AFTER it was set.
if oldfetchResultsController !== fetchedResultsController {
// Let's assume there is such method.
if fetchedResultsController.isBadAndNotSuitable == false {
// Do your stuff here.
} else {
// If you want to override value that was set
// without observers being called, you can just set
// your property inside didSet like:
fetchedResultsController = nil
// Or, if you don't want to change value:
fetchedResultsController = oldFetchedResultsController
// this won't cause willSet to fire and your property
// will preserve its value.
// It's ok to override observers in subclasses, because
// super's implementation will be called as well before.
}
}
}
}
As it were in 2014:
You may write it like this:
var _fetchedResultsController:NSFetchedResultsController? = nil
var fetchedResultsController:NSFetchedResultsController? {
get {
return _fetchedResultsController
}
set (aNewValue) {
if (_fetchedResultsController != aNewValue)
{
_fetchedResultsController = aNewValue
}
}
}
With this approach you can prevent value to be set. So, for example, if there was a property on NSFetchedResultsController called isBadAndNotSuitable you could write:
set (aNewValue) {
if (aNewValue.isBadAndNotSuitable)
{
NSLog("Don't set, it's bad")
}
else
{
_fetchedResultsController = aNewValue
}
}
Inside willSet you have a hidden var called newValue that you can use like you are doing in obj-c, like this:
willSet {
if newValue != fetchedResultsController {
//do stuff
}
}
This newValue variable contains the value you just tried to assign to the property.

Objective-C, inheritance between functions

What I want is to inherit trueCount and falseCount values from func1 to func2 as you can see in the code below.
Any help on how to do that ?
- (void)func1 {
int trueCount = 0;
int falseCount = 0;
if (test3) {
trueCount++;
} else {
falseCount++;
}
if (trueCount >= falseCount) {
NSLog(#"TRUE WINS !!";
} else if (trueCount < falseCount) {
NSLog(#"False WINS !!");
}
}
- (void)func2 {
///HOW to make trueCount AND falseCount work here too, so that they inherit the same value in func1 ??
if (trueCount >= falseCount) {
NSLog(#"Show 1 !!";
} else if (trueCount < falseCount) {
NSLog(#"Show 2");
}
}
Your example does not show functions, it shows methods. Neither methods nor functions can be inherited - only classes can inherit other classes. However, nothing stops you from calling one method from inside another method to share the results produced by it. You could also move the shared functionality into a "helper" method, and use it in both places, like this:
// Define the shared method. Note the use of pointers.
-(void)countTrue:(int*)trueCount andFalse:(int*)falseCount {
*trueCount = 0;
*falseCount = 0;
if (test3) {
*trueCount++;
} else {
*falseCount++;
}
}
- (void)func1 {
int trueCount;
int falseCount;
// Invoke the shared functionality from the first place in your code
[self countTrue:&trueCount andFalse:&falseCount];
if (trueCount >= falseCount) {
NSLog(#"TRUE WINS !!";
} else if (trueCount < falseCount) {
NSLog(#"False WINS !!");
}
}
- (void)func2 {
int trueCount;
int falseCount;
// Invoke the shared functionality from a second place in your code
[self countTrue:&trueCount andFalse:&falseCount];
if (trueCount >= falseCount) {
NSLog(#"Show 1 !!";
} else if (trueCount < falseCount) {
NSLog(#"Show 2");
}
}
Now the logic of counting is placed in a single place, so you do not need to repeat it.
You'll want to do something like this in your header:
#inteface GameCounter
#property (nonatomic) NSInteger trueCount;
#property (nonatomic) NSInteger falseCount;
#end
and then use self.trueCount and self.falseCount
This question really has nothing to do with iOS or Objective-C. It is an Object Oriented question.
You have methods in a class. Those methods operate on some state, configuring, modifying and querying that state. Your methods (they aren't functions) are called without parameters, so no state is being added. Your class should have state of its own.
#interface MyClass ()
#property (nonatomic) NSInteger trueCount;
#property (nonatomic) NSInteger falseCount;
#property (nonatomic) BOOL test3;
#end
#implementation MyClass
- (void)method1 {
if (self.test3) {
self.trueCount++;
} else {
self.falseCount++;
}
if (self.trueCount >= self.falseCount) {
NSLog(#"TRUE WINS !!";
} else if (self.trueCount < self.falseCount) {
NSLog(#"False WINS !!");
}
}
- (void)method2 {
if (self.trueCount >= self.falseCount) {
NSLog(#"Show 1 !!";
} else {
NSLog(#"Show 2");
}
}
#end

UIWebView Bug: -[UIWebView cut:]: unrecognized selector sent to instance

In the UIWebView, if an input element containing text has focus, and a button is pressed that causes the input to lose focus, then subsequently double-tapping on the input to regain focus and selecting Cut (or Copy or Paste) from the popup bar that appears causes the UIWebView to crash with the error:
-[UIWebView cut:]: unrecognized selector sent to instance 0x10900ca60
Demo project: https://github.com/guarani/WebViewDoubleTapTestTests.git
I think this must be a UIWebView bug, any ideas?
For completeness, here are the contents of my web view,
<html>
<head>
</head>
<body>
<br><br>
<input type="text">
<input type="button">
</body>
</html>
Filed a Bug Report at Apple: 15894403
Update 2019/05/30: Bug still present in iOS 12.0 (16E226)
This is an Apple bug. The problem is the cut: action is sent incorrectly in the responder chain, and ends up being sent to the UIWebView instance instead of the internal UIWebDocumentView, which implements the method.
Until Apple fixes the bug, let's have some fun with the Objective C runtime.
Here, I subclass UIWebView with the purpose of supporting all UIResponderStandardEditActions methods, by forwarding them to the correct internal instance.
#import ObjectiveC;
#interface CutCopyPasteFixedWebView : UIWebView #end
#implementation CutCopyPasteFixedWebView
- (UIView*)_internalView
{
UIView* internalView = objc_getAssociatedObject(self, "__internal_view_key");
if(internalView == nil && self.subviews.count > 0)
{
for (UIView* view in self.scrollView.subviews) {
if([view.class.description hasPrefix:#"UIWeb"])
{
internalView = view;
objc_setAssociatedObject(self, "__internal_view_key", view, OBJC_ASSOCIATION_ASSIGN);
break;
}
}
}
return internalView;
}
void webView_implement_UIResponderStandardEditActions(id self, SEL selector, id param)
{
void (*method)(id, SEL, id) = (void(*)(id, SEL, id))[[self _internalView] methodForSelector:selector];
//Call internal implementation.
method([self _internalView], selector, param);
}
- (void)_prepareForNoCrashes
{
NSArray* selectors = #[#"cut:", #"copy:", #"paste:", #"select:", #"selectAll:", #"delete:", #"makeTextWritingDirectionLeftToRight:", #"makeTextWritingDirectionRightToLeft:", #"toggleBoldface:", #"toggleItalics:", #"toggleUnderline:", #"increaseSize:", #"decreaseSize:"];
for (NSString* selName in selectors)
{
SEL selector = NSSelectorFromString(selName);
//This is safe, the method will fail if there is already an implementation.
class_addMethod(self.class, selector, (IMP)webView_implement_UIResponderStandardEditActions, "");
}
}
- (void)awakeFromNib
{
[self _prepareForNoCrashes];
[super awakeFromNib];
}
#end
Use this subclass in your storyboard.
Have fun.
If you don't mind that there is no callout for cut/paste/etc. in the case, when the UIWebview is wrongly becoming first responder, then you can also fix it with this category. This does not prohibit cut/paste/etc. when the UIWebDocumentView (correctly) becomes first responder.
#implementation UIWebView (NoWrongPerformWebview)
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
return NO;
}
#end
// Swift 4 compliant version
import UIKit
extension UIWebView {
override open func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
// Should be respond to a certain Selector ??
return responds(to: action)
}
}
If anyone is interested, here's the swift version of Leo Natans method :
import Foundation
import ObjectiveC
var AssociatedObjectHandle: UInt8 = 0
class CustomWebView: UIWebView {
func _internalView() -> UIView? {
var internalView:UIView? = objc_getAssociatedObject(self, "__internal_view_key") as? UIView
if internalView == nil && self.subviews.count > 0 {
for view: UIView in self.scrollView.subviews {
if view.self.description.hasPrefix("UIWeb") {
internalView = view
objc_setAssociatedObject(self, "__internal_view_key", view, objc_AssociationPolicy.OBJC_ASSOCIATION_ASSIGN)
}
}
}
return internalView
}
override func awakeFromNib() {
super.awakeFromNib()
self._prepareForNoCrashes()
}
func _prepareForNoCrashes() {
let selectors = ["cut:", "copy:", "paste:", "select:", "selectAll:", "delete:", "makeTextWritingDirectionLeftToRight:", "makeTextWritingDirectionRightToLeft:", "toggleBoldface:", "toggleItalics:", "toggleUnderline:", "increaseSize:", "decreaseSize:"]
for selName: String in selectors {
let selector = NSSelectorFromString(selName)
//This is safe, the method will fail if there is already an implementation.
let swizzledMethod:IMP = class_getInstanceMethod(CustomWebView.self, #selector(CustomWebView.webView_implement_UIResponderStandardEditActions))
class_addMethod(CustomWebView.self, selector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
}
}
func webView_implement_UIResponderStandardEditActions(this:AnyObject, selector:Selector, param:AnyObject)
{
let method = {(val1: UIView?, val2: Selector, val3: AnyObject) -> Void in
self._internalView()?.methodForSelector(selector)
}
method(self._internalView(), selector, param);
}
}
- (UIView *)_internalView {
UIView *internalView = nil;
if (internalView == nil && self.subviews.count > 0) {
for (UIView *view in self.scrollView.subviews) {
if([view.class.description hasPrefix:#"UIWeb"]) {
internalView = view;
break;
}
}
}
return internalView;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
struct objc_method_description methodDescription = protocol_getMethodDescription(#protocol(UIResponderStandardEditActions), aSelector, NO, YES);
if (methodDescription.name == aSelector) {
UIView *view = [self _internalView];
if ([view respondsToSelector:aSelector]) {
return view;
}
}
return [super forwardingTargetForSelector:aSelector];
}

Can I use custom values of UIControlState for my own control?

Is there a way to set a custom states -- not one of the existing UIControlState values -- for a UIControl?
In the UIControlSate enum, there are 16 bits that can be used for custom control states:
UIControlStateApplication = 0x00FF0000, // additional flags available for application use
The problem is that UIControl's state property is readonly.
I want to set different background images to my UIButton for custom states.
You can make use of the custom states in a subclass of UIControl.
Create a variable called customState in which you will manage your custom states.
If you need to set a state, do your flag operations against this variable, and call [self stateWasUpdated].
Override the state property to return [super state] bitwise OR'd against your customState
Override the enabled, selected and highlighted setters so that they call [self stateWasUpdated]. This will allow you to respond to any changes in state, not just changes to customState
Implement stateWasUpdated with logic to respond to changes in state
In the header:
#define kUIControlStateCustomState (1 << 16)
#interface MyControl : UIControl {
UIControlState customState;
}
In the implementation:
#implementation MyControl
-(void)setCustomState {
customState |= kUIControlStateCustomState;
[self stateWasUpdated];
}
-(void)unsetCustomState {
customState &= ~kUIControlStateCustomState;
[self stateWasUpdated];
}
- (UIControlState)state {
return [super state] | customState;
}
- (void)setSelected:(BOOL)newSelected {
[super setSelected:newSelected];
[self stateWasUpdated];
}
- (void)setHighlighted:(BOOL)newHighlighted {
[super setHighlighted:newHighlighted];
[self stateWasUpdated];
}
- (void)setEnabled:(BOOL)newEnabled {
[super setEnabled:newEnabled];
[self stateWasUpdated];
}
- (void)stateWasUpdated {
// Add your custom code here to respond to the change in state
}
#end
Based on #Nick answer I have implemented a simpler version. This subclass exposes a BOOL outlined property that is similar in function to selected, highlighted and enabled.
Doing things like [customButtton setImage:[UIImage imageNamed:#"MyOutlinedButton.png"] forState:UIControlStateOutlined] makes it automagically work when you update the outlined property.
More of these state + property could be added if needed.
UICustomButton.h
extern const UIControlState UIControlStateOutlined;
#interface UICustomButton : UIButton
#property (nonatomic) BOOL outlined;
#end
UICustomButton.m
const UIControlState UIControlStateOutlined = (1 << 16);
#interface OEButton ()
#property UIControlState customState;
#end
#implementation OEButton
- (void)setOutlined:(BOOL)outlined
{
if (outlined)
{
self.customState |= UIControlStateOutlined;
}
else
{
self.customState &= ~UIControlStateOutlined;
}
[self stateWasUpdated];
}
- (BOOL)outlined
{
return ( self.customState & UIControlStateOutlined ) == UIControlStateOutlined;
}
- (UIControlState)state {
return [super state] | self.customState;
}
- (void)stateWasUpdated
{
[self setNeedsLayout];
}
// These are only needed if you have additional code on -(void)stateWasUpdated
// - (void)setSelected:(BOOL)newSelected
// {
// [super setSelected:newSelected];
// [self stateWasUpdated];
// }
//
// - (void)setHighlighted:(BOOL)newHighlighted
// {
// [super setHighlighted:newHighlighted];
// [self stateWasUpdated];
// }
//
// - (void)setEnabled:(BOOL)newEnabled
// {
// [super setEnabled:newEnabled];
// [self stateWasUpdated];
// }
#end
I'd like to offer a slight refinement to this strategy. See this stackoverflow question:
Overriding isHighlighted still changes UIControlState - why?
It turns out that Apple's state implementation is actually a computed property based off the other properties, isSelected, isHighlighted, isEnabled, etc.
So there is actually no need for a custom state bit mask on top of UIControlState (well, it's not that there is no need, it's just that it's adding complexity where there need/ought not be).
If you wanted to be congruent with Apple's implementation, you would just override the state property and check your custom states in the getter.
extension UIControlState {
static let myState = UIControlState(rawValue: 1 << 16)
}
class MyControl: UIControl {
override var state: UIControlState {
var state = super.state
if self.isMyCustomState {
state.insert(UIControlState.myState)
}
return state
}
var isMyCustomState: Bool = false
}
It's actually a smart way to go; as per the link above, if you override the property and don't change the state you will get inconsistent results. Making state always a computed property ensures consistency between the properties that state represents.
Swift 3 version of Nick's answer:
extension UIControlState {
static let myState = UIControlState(rawValue: 1 << 16)
}
class CustomControl: UIControl {
private var _customState: UInt = 0
override var state: UIControlState {
return UIControlState(rawValue: super.state.rawValue | self._customState)
}
var isMyCustomState: Bool {
get {
return self._customState & UIControlState.myState.rawValue == UIControlState.myState.rawValue
} set {
if newValue == true {
self._customState |= UIControlState.myState.rawValue
} else {
self._customState &= ~UIControlState.myState.rawValue
}
}
}
}

Resources