I'm using a container UIView to house a UIImageView and do some custom drawing. At this point I'd like to do some drawing on top of my subview. So overriding drawRect: in my container UIView will only draw below the subviews.
Is there a way to overload drawRect: in my subview without subclassing it?
I think method swizzling may be the answer, but I'm hoping not.
(NOTE: yes, it would have been smarter to have the UIView be the subview of the UIImageView, but unfortunately I'm committed to my mistake now.)
Are you sure you mean overload and not override?
Overloading -- creating a new method with the same basic name but different arguments and therefore a different selector -- could be accomplished by adding a new method category containing your new method to the existing class.
Overriding -- modifying the behavior of an existing method -- would require either monkey-patching the class's method table at runtime (for example, by swizzling) or subclassing.
Related
I have a case where I need to modify all instances of NSView, trivial by adding a couple of lines of code to init() but how can that be done cleanly for all subclasses in Swift?
The case I have in mind:
I add a custom property to NSView via an extension
That works fine, but isn't animatable until it's registered as animatable via the layer's addAnimation:forKey: method.
So all I really need to do is ensure that addAnimation:forKey: gets called every time NSView or a subclass gets called.
Things I've considered:
Somehow doing this in an extension. Problem: extensions can't override functions, and without overriding init() or something like viewDidAppear() the code has to be called manually every time (e.g. with a convenience initialiser).
Subclass NSView and add the code. Problem: NSButton, Slider and all the rest won't inherit the code, and I have to subclass them all.
Method swizzling. This should be possible, because obj-c methods are swizzlable and NSView inherits from NSObject.
So far only swizzling seems viable, but I'm reluctant to do this because it's reliant on the APIs staying obj-c friendly, and I don't know how long that will be the case for.
Is there another way?
Across my app I have several different subclasses of UIView: UIDatePicker, UIPicker, UIButton, UITableView, UITableViewCell, UITextView, etc. etc... For each of these I'd like to add a very simple drawRect custom implementation that I have working great.
Is there a simple way to get multiple subclasses of UIView to all have the same drawRect implementation without creating a subclass and repeating the same code across each UIPicker, UIButton, etc. etc... ?
I realize the solution to this is probably to write a delegate class for UIView's layer property and do the custom drawing in drawLayer, but I thought I would ask before I go re-working my code.
The answer seems to be no.
Furthermore, it appears that my idea for the workaround also doesn't work: I was thinking I could write a nice little class that implements the drawLayer method from CALayerDelegate and do the drawing in there, and then in each UIView or UIView subclass' init method do a self.layer.delegate = niceLittleCALayerDelegateClass.
My research, however, happened upon this: https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CALayer_class/#//apple_ref/occ/instp/CALayer/delegate which contains the damning sentence: In iOS, if the layer is associated with a UIView object, this property must be set to the view that owns the layer.
So, double nope.
Against rmaddy's advice I'm going to just write a subclass for each of the UIView subclasses I want to implement this drawing behavior in. We'll see how that goes.
Update:
I can't believe I didn't think of this before, but the "right" way to do this (that is to say without subclassing UIView's subclasses and adding a custom drawRect method to each subclass's subclass), from everything I can find, seems to be to either create a subview or a sublayer with a transparent background that does whatever custom drawing you want.
Obviously this is going to draw on top of the UIView you're actually using, so this would get exceedingly complicated if you're trying to draw things that interact with the default elements of the UIView, but for my purposes (just a simple frame drawn with a UIBezierPath) it seems to work great.
At present I'm not sure if the subview or the sublayer approach is more efficient. If anyone can shed light on that, I'd appreciate it.
I'm trying to refactor my code in the best possible way and I'm wondering what the proper architecture for the given situation is.
What I'm Trying To Do
What I'm doing is pretty simple: I have some custom CALayer subclasses that represent an interactive UI element. They are broken up into multiple layers since some of the parts of the UI are static, so I didn't want to redraw those static elements needlessly. Right now, the layers are added as sublayers in the initialization part of a CustomView class that is a subclass of UIView.
There is currently no corresponding CustomViewController class that is a subclass of UIViewController because when I'm using the CustomView, it's contained within a UITableViewCell or a part of a generic UIViewController with other views in it, so I felt another UIViewController for each CustomView instance would be redundant.
Also of importance is the that the only operation that I'm doing inside of the UIView class is I'm responding to touch events and sending the touch information to the sublayers so that the UI can update its appearance accordingly. I'm not overriding the drawRect method or anything like that.
The Question
Basically, I'm trying to figure out whether I should either:
Option 1:
Get rid of the CustomView class, create a CustomViewController class that is a subclass of UIViewController, and simply add the CALayer objects as sublayers of the CustomViewController's built-in view property.
or
Option 2:
My thinking about the UIViewController subclass being redundant is correct, so I should leave it the way I have it and have a CustomView class with the CALayer objects inside of it.
I would highly appreciate any advice on this.
I think that in terms of MVC, the code you're describing (option #2) is well written and maintains a very clear boundary of responsibility. You're not writing any code that has nothing to do with the view layer itself in this class which is great. I think that in this case there's no need for a separate UIViewController subclass to manage these instances because as you said - they are handling their own touch events and visible layers (exactly their responsibility).
If for any reason there is a need for something more complex that requires data related logic or other such computation, I would definitely consider subclassing a UIViewController or maybe looking at the problem in an entirely different way.
Given the situation you've presented, I think that maintaining the CALayer instance within this UIView subclass ('CustomView') is the right way to go.
I'm working on an accessibility project for an iOS application. Because accessibility does not act quite as advertised, I have to override accessibilityFrame, accessibilityActivationPoint and pointInside:withEvent in a subclass in order to expand the region recognized by VoiceOver (for both drawing and touch recognition) beyond the "natural" bounds of the control view. So, in order to change the VoiceOver bounds of a UIButton I have to subclass that class and then add these three methods. In order to do this for a UILabel I have to add another subclass with the code, and so on.
I can refactor the code in these methods to a central location, but I was wondering if this can be done more elegantly with inheritance. I'd like to put this code into a subclass of UIView (maybe called UIViewAccessible) and then create a subclass of UIButton called UIButtonAccessible which inherits from UIButton which would in turn inherit from UIViewAccessible instead of UIView. Is this possible, or can something like this be done with a category?
Edit: According to the docs, you can't really achieve this with a category:
If the name of a method declared in a category is the same as a method in the original class, or a method in another category on the same class (or even a superclass), the behavior is undefined as to which method implementation is used at runtime.
Is there some other way to do this?
To answer your question, no, it can't, since your UIViewAccessible is a second degree sibling to UIButton in the inheritance chain (both inherit from UIView at some point). But I guess you already knew that. As for a solution, you could wrap around your UIView accessible classes a decorator and use protocols for strong typing. That way you'll keep the code in one place. I've described this technique here in more detail (although for a different purpose, it's the same situation).
For the views that would support accessibility you'll have to do this:
#property (nonatomic, strong) UIView<MyAccesibilityProtocol>* view;
//self.view can come from the nib or previously created in code
self.view = [[AccesibilityDecorator alloc] initWithDecoratedObject:self.view];
//you can then use self.view like any other UIView,
//and because it also implements an
//accessibility protocol, you can use the methods
//implemented in the wrapper as well.
//more than that, you can control which methods to override
//in the AccesibilityDecorator class
[self.view addSubview:otherView];//could be overridden or not
[self.view myAccesibilityMethod];//custom method declared in the protocol
(iPhone SDK 3.x:) I have a UIControl subclass that creates a different number of subviews depending on the length of an NSArray property. Please take my word for it that this needs to be a UIControl rather than a UIView.
Currently I implement subview management in drawRect, beginning by removing all subviews and then creating the appropriate number based on the property. I don't think this is very good memory management and I'm not sure if drawRect is really the appropriate place to add subviews. Any thoughts on the best way to handle this pattern?
Thank you.
There is a method called layoutSubviews, and like the name already says, that method is thought to layout the subviews. You can call setNeedsLayout and the layoutSubviews method will be called (do not call layoutSubviews directly).