Apply global class to UIImage - ios

Now I'm not sure if this is possible in Objective-C but I would expect it should be.
I have multiple images across my storyboard using the following/similar code on UIImages
cell.followingImage.layer.cornerRadius = cell.followingImage.frame.size.width / 2;
cell.followingImage.clipsToBounds = YES;
I want to optimise the code. I know you can apply classes to objects but I have never done this before. Is it possible to apply the class via the storyboard and automatically run that code without referencing and adding it to every controller?
So if a class is attached to the UIImage it would just run that code and make the UIImage a circle? This is probably really simple...
If I was to subclass a UIimage I'm not sure where to put the above code?

Try making a category of UIImage. Include everything you want in this custom category class like your cornerRadius and your clipToBounds code. Then when you init the images, don't init the images with [UIImage new] but with [customImageCategoryName new[. Now, all these images will, by default, have those two lines of code. The following link will show you how to create a category in Xcode How do I create a category in Xcode 6 or higher?

Do place that code in
-(instancetype)initWithCoder:(NSCoder *)aDecoder method , -(instancetype)initWithFrame:(CGRect)frame method
and init method.
That way if the imageview is from storyboard the initWithCoder will get called and other methods get called when it is programatically added.

As none of the other answers really worked nor were complete enough I used User Defined Runtime Attributes instead.
To avoid the need to write it in code I added
layer.cornerRadius with Number attribute half the width.
layer.masksToBounds with Bool attribute as YES

Method swizzling is a very good option to achieve this.
Create a UIImageView category class and swizzle initWithCoder: method
#import "UIImageView+MethodSwizzling.h"
#import <objc/runtime.h>
#implementation UIImageView (MethodSwizzling)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = #selector(initWithCoder:);
SEL swizzledSelector = #selector(xxx_initWithCoder:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);
// ...
// Method originalMethod = class_getClassMethod(class, originalSelector);
// Method swizzledMethod = class_getClassMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
}
else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (id)xxx_initWithCoder:(NSCoder*)aDecoder {
[self xxx_initWithCoder:aDecoder];
self.layer.cornerRadius = self.frame.size.width / 2;
self.layer.masksToBounds = YES;
NSLog(#"xxx_initWithCoder: %#", self);
return self;
}
You don't have to create subclass of UIImageView and do not need to change your object's class everywhere in XIB/Storybord.
Click here to know about method swizzling.

What your "answer" suggests is that you are using UIImageViews in the Storyboard. The best approach would be to create a subclass of UIImageView, e.g.:
//MyImageView.h
#import <UIKit/UIKit.h>
#interface MyImageView : UIImageView
#end
The code to adapt the cornerRadius and the clipsToBounds should be added to the MyImageView.m:
//MyImageView.m
- (id)layoutSubviews {
[super layoutSubviews];
self.layer.cornerRadius = self.view.frame.width.width / 2
self.layer.clipsToBounds = true;
// this line may be only needed in init since the clipsToBounds does not change due to autoLayout.
}
After writing that code, you need to set the class attribute of all UIImageViews in the Storyboard (all those that should be circular) to MyImageView instead of the default UIImageView.

Related

How to hide the home indicator for iPhone X writing a common code in app delegate

In my project i would like to hide the home indicator without writing the same code in every view controller and instead implement it in the appDelegate. I tried
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor yellowColor];
return YES;
}
-(BOOL)prefersHomeIndicatorAutoHidden{
return YES;
}
I tried like this but its not working commonly. So how can i hide the home indicator without writing in every view controllers and instead implementing from app delegate itself?
There is a way to accomplish this. In my opinion it's not the most elegant and I would agree with Gereon that you'd be better off with creating a subclass of UIViewController, implement it there and then have all your view controllers inherit from that base class.
You can however accomplish this using Method Swizzling. See it here: https://nshipster.com/method-swizzling/. In your case you can swizzle it in AppDelegate in application: didFinishLaunchingWithOptions: and swizzle prefersHomeIndicatorAutoHidden to your custom function that returns YES.
So for swizzling I'd suggest you create a new Category of the UIViewController. And the actual swizzling:
#import <objc/runtime.h>
#implementation UIViewController (Swizzling)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = #selector(prefersHomeIndicatorAutoHidden);
SEL swizzledSelector = #selector(swizzledPrefersHomeIndicatorAutoHidden);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
const BOOL didAdd = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAdd)
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
else
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
- (BOOL)prefersHomeIndicatorAutoHidden
{
return YES; //Doesn't matter what you return here. In this you could return the actual property value.
}
- (BOOL)swizzledPrefersHomeIndicatorAutoHidden //This is the actual `prefersHomeIndicatorAutoHidden ` call
{
return YES;
}
#end

Remove shadow on IIViewDeckController 2.2.11

How to remove the shadow of the centerViewController on IIViewDeckController 2.2.11 ? I know I have to use a delegate that implements the viewDeckController:applyShadow:withBounds: selector. But I don't know how to do that.
If someone can help me. Thank you
There is a property in the IIViewDeckController called 'shadowEnabled' simply set it to NO in your IIViewDeckController instance variable.
Alternatively in your Storyboard or .Xib file, you can add a User Defined Runtime Attribute with 'shadowEnabled' as the Key Path, 'Boolean' as the type and uncheck the value (making it NO/False)
So I found the solution for the version 2.2.11.
I added this to AppDelegate.h:
#import "IIViewDeckController.h"
#interface AppDelegate : UIResponder <UIApplicationDelegate, IIViewDeckControllerDelegate>
In AppDelegate.m, in didFinishLaunchingWithOptions class:
deckController.delegate = self;
Then I added the viewDeckController:applyShadow:withBounds: selector to the end of AppDelegate.m:
- (void)viewDeckController:(IIViewDeckController *)viewDeckController applyShadow:(CALayer *)shadowLayer withBounds:(CGRect)rect {
shadowLayer.masksToBounds = NO;
shadowLayer.shadowRadius = 0;
shadowLayer.shadowOpacity = 0;
shadowLayer.shadowColor = nil;
shadowLayer.shadowOffset = CGSizeZero;
shadowLayer.shadowPath = nil;
}

objective c how to set exclusive touch for all UIbuttons on the whole app

My app has many buttons through out the application
I want to set Exclusive Touch all of them together at one time. or all views in the app
we can set individually by
[button setExclusiveTouch:YES];
But i want to set at a time for all the buttons in the application
Can we set all view exclusive touch ?
any body have any idea please suggest me.
The most elegant and actually the designed way to do this is by using the appearance proxy, which is designed to set a defined behaviour or appearance across the board for a given UI component.
[[UIButton appearance] setExclusiveTouch:YES];
For more information: Apple Documentation - UIAppearance and NSHipster - UIAppearance
You can try this
// Not tested
for (UIView * button in [myView subviews]) {
if([button isKindOfClass:[UIButton class]])
[((UIButton *)button) setExclusiveTouch:YES];
}
If you really want to set exclusiveTouch for ALL UIButtons + subclasses in your whole application and not just a single view, you can use method swizzling.
You use the objc runtime to override willMoveToSuperview and set exclusive touch there.
This is very reliable and i've never had any problems using this technique.
I like doing this especially for UISwitch, because touch-handling for switches can be a bit tricky and exclusiveTouch will help avoid bugs caused by simultaneous taps on different switches
Sample taken from the link above and changed so that exclusiveTouch is set:
#import <objc/runtime.h>
#implementation UIButton (INCButton)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = #selector(willMoveToSuperview:);
SEL swizzledSelector = #selector(inc_willMoveToSuperview:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)inc_willMoveToSuperview:(UIView *)newSuperview
{
// This is correct and does not cause an infinite loop!
// See the link for an explanation
[self inc_willMoveToSuperview:newSuperview];
[self setExclusiveTouch:YES];
}
#end
Create a category and insert this code for each class you want to alter.
why so difficult? Make a category
#implementation UIButton (ExclusiveTouch)
- (BOOL)isExclusiveTouch
{
return YES;
}
#end
Cycle through the subviews (recursively) of the top most view, and for each object of type UIButton, apply exclusive touch

Method Swizzling not firing correctly?

I am following this article to better understand how method swizzling works. I have my main view controller (this is a new project) like this:
#import "GLViewController.h"
#import <objc/runtime.h>
#implementation UIViewController (Tracking)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [UIViewController class];
SEL originalSelector = #selector(viewWillAppear:);
SEL swizzledSelector = #selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(#"viewWillAppear: %#", self);
}
#end
#implementation GLViewController
-(void)viewWillAppear:(BOOL)animated
{
NSLog(#"glviewappear");
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
If I run this, then it prints glviewappear, and if I remove
-(void)viewWillAppear:(BOOL)animated
{
NSLog(#"glviewappear");
}
then it prints viewWillAppear: <GLViewController: 0x9d11a90>. My project needs to be able to fire on both of these methods. Is there a way to do this?
There is a simple reason. You are not calling the UIViewController implementation since you are not calling [super viewWillAppear:animated].
Just do:
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(#"glviewappear");
}
Here is a more detailed explanation
Each method in an Objective C class is nothing more than a simple function, referenced through a pointer saved in a dispatch table: the keys are the selectors, and their associated values are the pointers to each method implementation (the IMP).
When you swizzle, you just swap two pointers in the dispatch table, in order to swap the referenced functions. Since the dispatch table is attached to the class, swizzling happens only in the class on which you perform it, and not in the subclasses.
In your case, there are 3 different functions in play:
UIViewController has the pointers to the following functions in the dispatch table. These functions get swapped at runtime through swizzling.
viewWillAppear: (now pointing to xxx_viewWillAppear: implementation)
xxx_viewWillAppear: (now pointing to viewWillAppear: implementation)
GLViewController has another pointer, to its own implementation of viewWillAppear:.
If you don't call super, you are not accessing the dispatch table of the UIViewController class, so you are not invoking the implementation at all.
When you delete your viewWillAppear: method, obviously it works, since the super implementation gets automatically called.

UITextView style is being reset after setting text property

I have UITextView *_masterText and after call method setText property font is being reset.
It's happening after I change sdk 7.
_masterText is IBOutlet, global and properties are set in storyboard. It's only me or this is general SDK bug?
#interface myViewController : UIViewController
{
IBOutlet UITextView *_masterText;
}
#implementation myViewController
-(void)viewWillAppear:(BOOL)animated
{
[_masterText setText:#"New text"];
}
Sitting with this for hours, I found the bug.
If the property "Selectable" = NO it will reset the font and fontcolor when setText is used.
So turn Selectable ON and the bug is gone.
I ran into the same issue (on Xcode 6.1) and while John Cogan's answer worked for me, I found that extending the UITextView class with a category was a better solution for my particular project.
interface
#interface UITextView (XcodeSetTextFormattingBugWorkaround)
- (void)setSafeText:(NSString *)textValue;
#end
implementation
#implementation UITextView (XcodeSetTextFormattingBugWorkaround)
- (void)setSafeText:(NSString *)textValue
{
BOOL selectable = [self isSelectable];
[self setSelectable:YES];
[self setText:textValue];
[self setSelectable:selectable];
}
#end
If you want your text view to be "read only" you can check Editable and Selectable and uncheck User Interaction Enabled, with this the UITextView was behaving as I wanted
Had this issue myself and the above answer helped but I added a wrapper to my ViewController code as follows and just pass the uiview instance and text to change and the wrapper function toggles the Selectable value on, changes text and then turns it off again. Helpful when you need the uitextview to be off at all times by default.
/*
We set the text views Selectable value to YES temporarily, change text and turn it off again.
This is a known bug that if the selectable value = NO the view loses its formatting.
*/
-(void)changeTextOfUiTextViewAndKeepFormatting:(UITextView*)viewToUpdate withText:(NSString*)textValue
{
if(![viewToUpdate isSelectable]){
[viewToUpdate setSelectable:YES];
[viewToUpdate setText:textValue];
[viewToUpdate setSelectable:NO];
}else{
[viewToUpdate setText:textValue];
[viewToUpdate setSelectable:NO];
}
}
EDIT :
Setting font for UITextView in iOS 7 work for me if firstly you set the text and after that you set the font :
#property (nonatomic, weak) IBOutlet UITextView *masterText;
#implementation myViewController
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
_myTextView.text = #"My Text";
_myTextView.font = [UIFont fontWithName:#"Helvetica.ttf" size:16]; // Set Font
}
On a XIB file, if you add some text in your UITextView and change the font or the color it will work.
Here's a quick subclass solution I often use for this problem.
class WorkaroundTextView: UITextView {
override var text: String! {
get {
return super.text
}
set {
let originalSelectableValue = self.selectable
self.selectable = true
super.text = newValue
self.selectable = originalSelectableValue
}
}
}
This issue resurfaced in Xcode 8. This is how I fixed it:
Changed the extension to:
extension UITextView{
func setTextAvoidXcodeIssue(newText : String, selectable: Bool){
isSelectable = true
text = newText
isSelectable = selectable
}
}
and checked the Selectable option in the Interface Builder.
It's not very elegant to have that 'selectable' parameter but it'll do.
In iOS 8.3, the workaround of setting "selectable" to YES before the setText, and NO after, didn't fix it for me.
I found I needed to set "selectable" to YES in the storyboard, too, before this would work.
This worked for me:
let font = textView.font
textView.attributedText = attributedString
textView.font = font
For me with attributed text, I just needed to set the font in the attributes dictionary rather than setting it in it's own field.
I am having this problem to.
A swifty-friendly solution of #Ken Steele's answer answer.
I extend the UITextView and add a computed property.
extension UITextView {
// For older Swift version output should be NSString!
public var safeText:String!
{
set {
let selectable = self.selectable;
self.selectable = true;
self.text = newValue;
self.selectable = selectable;
}
get {
return self.text;
}
}
}
hope it helps.
Its been 3 years and the bug still exists in the latest stable version of Xcode (7.3). Clearly apple wont be fixing it any time soon leaving developers with two options: leaving selectable on and setting UserInteractionEnabled to false or Method swizzling.
If you have a button on your textView the former will not suffice.
No-code-change-requied solution in swift:
import UIKit
extension UITextView {
#nonobjc var text: String! {
get {
return performSelector(Selector("text")).takeUnretainedValue() as? String ?? ""
} set {
let originalSelectableValue = selectable
selectable = true
performSelector(Selector("setText:"), withObject: newValue)
selectable = originalSelectableValue
}
}
}
Objective-C:
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
#implementation UITextView (SetTextFix)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = #selector(setText:);
SEL swizzledSelector = #selector(xxx_setText:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)xxx_setText:(NSString *)text {
BOOL originalSelectableValue = self.selectable;
self.selectable = YES;
[self xxx_setText:text];
self.selectable = originalSelectableValue;
}
#end
Using the work-around discussed in this issue, this extension to UITextView provides a setTextInCurrentStyle() function. Based on solution by Alessandro Ranaldi but does not require the current isSelectable value to be passed to the function.
extension UITextView{
func setTextInCurrentStyle(_ newText: String) {
let selectablePreviously = self.isSelectable
isSelectable = true
text = newText
isSelectable = selectablePreviously
}
}

Resources