I want to implement a UIScrollView subclass to present some custom formatted content. I just set a model object property of the scroll view and it handles all the required layout and rendering to display the content.
This works fine, but now I'd like to include zooming. According to the documentation, to support zooming you have to set a delegate and implement the viewForZoomingInScrollView: method. I guess I could set the delegate to the scroll view itself and implement that method in the subclass. But doing that I would lose the ability to have an external delegate (like an encapsulating UIViewController) that can be notified about scroll events.
Assuming the documentation is right and there is absolutely no (documented) way to implement zooming without a delegate, how could I still retain the possibility of having a regular, unrelated delegate?
Building upon H2CO3's suggestion of saving a hidden pointer to the real delegate and forwarding all incoming messages to it, I came up with the following solution.
Declare a private delegate variable to store a reference to the "real" delegate that is passed in to the setDelegate: method:
#interface BFWaveScrollView ()
#property (nonatomic, weak) id<UIScrollViewDelegate> ownDelegate;
#end
Set the delegate to self to be notified about scrolling events. Use super, so the original setDelegate: implementation is called, and not our modified one.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[super setDelegate:self];
}
return self;
}
Override setDelegate: to save a reference to the "real" delegate.
- (void)setDelegate:(id<UIScrollViewDelegate>)delegate {
_ownDelegate = delegate;
}
When the UIScrollView tries to call a method of its delegate, it will first check to see if the delegate respondsToSelector:. We have to forward this to the real delegate if the selector is part of the UIScrollViewDelegate protocol (Don't forget to #import <objc/runtime.h>).
- (BOOL)selectorIsScrollViewDelegateMethod:(SEL)selector {
Protocol *protocol = objc_getProtocol("UIScrollViewDelegate");
struct objc_method_description description = protocol_getMethodDescription(
protocol, selector, NO, YES);
return (description.name != NULL);
}
- (BOOL)respondsToSelector:(SEL)selector {
if ([self selectorIsScrollViewDelegateMethod:selector]) {
return [_ownDelegate respondsToSelector:selector] ||
[super respondsToSelector:selector];
}
return [super respondsToSelector:selector];
}
Finally, forward all delegate methods to the real delegate that are not implemented in the subclass:
- (id)forwardingTargetForSelector:(SEL)selector {
if ([self selectorIsScrollViewDelegateMethod:selector]) {
return _ownDelegate;
}
return [super forwardingTargetForSelector:selector];
}
Don't forget to manually forward those delegate methods that are implemented by the subclass.
I'd abuse the fact that I'm being a subclass (on purpose :P). So you can hack it. Really bad, and I should feel bad for proposing this solution.
#interface MyHackishScrollView: UIScrollView {
id <UIScrollViewDelegate> ownDelegate;
}
#end
#implementation MyHackishScrollView
- (void)setDelegate:(id <UIScrollViewDelegate>)newDel
{
ownDelegate = newDel;
[super setDelegate:self];
}
- (UIView *)viewForScrollingInScrollView:(UIScrollView *)sv
{
return whateverYouWant;
}
// and then implement all the delegate methods
// something like this:
- (void)scrollViewDidScroll:(UIScrollView *)sv
{
[ownDelegate scrollViewDidScroll:self];
}
// etc.
#end
Maybe this is easier to read and understand a couple of weeks later :)
(sample code for intercepting locationManager:didUpdateLocations: in a subclass)
Other than that the same handling for setting self as delegate to the superclass and intercepting setDelegate in order to save the user's delegate to mDelegate.
EDIT:
-(BOOL)respondsToSelector:(SEL)selector {
if (sel_isEqual(selector, #selector(locationManager:didUpdateLocations:)))
return true;
return [mDelegate respondsToSelector:selector];
}
- (id)forwardingTargetForSelector:(SEL)selector {
if (sel_isEqual(selector, #selector(locationManager:didUpdateLocations:)))
return self;
return mDelegate;
}
Related
I have a UIViewController subclass (say MyViewController).
MyViewController.h
#protocol TargetChangedDelegate
-(void) targetChanged;
#end
#interface MyViewController
#property (weak) id<TargetChangedDelegate> targetChangedDelegate;
-(void) doSomethingOnYourOwn;
#end
MyViewController.m
#implementation MyViewController <TargetChangedDelegate>
-(void) doSomethingOnYourOwn
{
// DO some stuff here
// IS THIS BAD ??
self.targetChangedDelegate = self;
}
-(IBAction) targetSelectionChanged
{
[self.targetChangedDelegate targetChanged];
}
-(void) targetChanged
{
// Do some stuff here
}
#end
Based on certain conditions a class that instantiates an instance of MyViewController may decide to set itself as the delegate or not.
Foo.m
#property(strong) MyViewController *myVC;
-(void) configureViews
{
self.myVC = [[MyViewController alloc] init];
[self.view addSubview:self.myVC];
if (someCondition)
{
self.myVC.targetChangedDelegate = self;
}
else
{
[self.myVC doSomethingOnYourOwn]
//MyViewController sets itself as the targetChangedDelegate
}
}
With reference to the code snippet above, I have the following question:
Is it a violation of MVC/delegation design pattern (or just a bad design) to say:
self.delegate = self;
There's absolutely no problem with setting the delegate to self. In fact it is a good way to provide default delegate functionality if a delegate is not set by somebody else.
Obviously, the delegate property has to be declared weak otherwise you get a reference cycle.
To expand a bit, having read the wrong answer and wrong comments above, if you allow an object to be its own delegate, your code is cleaner because you do not have to surround absolutely every single delegate call with
if ([self delegate] != nil)
{
[[self delegate] someMethod];
}
else
{
[self someMethod];
}
Its not proper way to assign self.delegate = self.
for your functionality, you can do this:
-(void) doSomethingOnYourOwn
{
// DO some stuff here
self.targetChangedDelegate = nil;
}
and when using delegate:
if(self.targetChangedDelegate != nil && [self.targetChangedDelegate respondsToSelector:#selector(targetChanged)]
{
[self.targetChangedDelegate targetChanged];
}
else
{
[self targetChanged];
}
It is bad design to set self.delegate = self; it should be another object. Delegation via protocols are an alternative design to subclassing and you can read more about delegation here:
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html
And here is more on protocols:
https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/Protocol.html
I am a little new to iOS development, coming from a Java / Android background. My understanding is that your custom Protocols or Delegates are like Interfaces in Java land.
If that is the case then I believe these Protocols are also Objects as well.
Case:
Assume 2 ViewControllers, Home and Profile.
1 Presenter, let's call it StuffPresenter gets instantiated individually in both ViewControllers.
StuffPresenter has an initialization method called initWithInteractor that takes in a parameter of Interactor which is a protocol.
Both Home and Profile implement a Protocol called Interactor, which has a method called initStuffInTableView(NSMutableArray *)stuff.
So I have a dilemma where if I am in Home and StuffPresenter relays information then I switch over to Profile, StuffPresenter loads stuff in Home as well as Profile.
Why is this the case?
Here is the code I have setup:
Protocol
#protocol Interactor <NSObject>
- (void)initStuffInTableView:(NSMutableArray *)stuff;
#end
Presenter
#interface Presenter : NSobject
- (id)initWithInteractor:(id<Interactor>)interactor;
- (void)loadStuff;
#end
#implementation {
#private
id<Interactor> _interactor;
}
- (id)initWithInteractor:(id<Interactor>)interactor {
_interactor = interactor;
return self;
}
- (void)loadStuff {
// Load stuff
NSMutableArray *stuff = // Init stuff in array...
[_interactor initStuffInTableView:stuff];
}
#end
Home
#interface HomeViewController : UITableViewController <Interactor>
- (void)initPresenter;
#end
#implementation {
#private
StuffPresenter *_stuffPresenter;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self initPresenter];
[self initPullToRefresh];
}
# pragma mark - init
- (void)initPresenter {
_stuffPresenter = [[StuffPresenter alloc] initWithInteractor:self];
}
- (void)initPullToRefresh {
// Init pull to refresh
// ...
[self.refreshControl addTarget:self
action:#selector(reloadStuff)
forControlEvents:UIControlEventValueChanged];
}
# pragma mark - Interactor
- (void)initStuffInTableView:(NSMutableArray *)stuff {
// Do some work
[self.tableView reloadData];
}
# pragma mark - reloadStuff
- (void)reloadStuff {
[_stuffPresenter loadStuff];
}
# pragma mark - TableView methods here
// TableView methods...
#end
Profile
#interface ProfileViewController : UITableViewController <Interactor>
- (void)initPresenter;
#end
#implementation {
#private
StuffPresenter *_stuffPresenter;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self initPresenter];
}
# pragma mark - init
- (void)initPresenter {
_stuffPresenter = [[StuffPresenter alloc] initWithInteractor:self];
[_stuffPresenter loadStuff];
}
# pragma mark - Interactor
- (void)initStuffInTableView:(NSMutableArray *)stuff {
// Do some work
[self.tableView reloadData];
}
# pragma mark - TableView methods here
// TableView methods...
#end
Problem:
When I go to Profile the app crashes, because initStuffInTableView is being called in Home. Why is this the case?
A protocol is an Objective-C language feature for specifying that a Class (or another protocol) has certain features, for the benefit of the compiler/ARC/the programmer.
A delegate, or delegation, is a design pattern which makes Model View Controller easier. To make the object doing the delegation be more flexible, generally its delegate adopts a protocol.
There are a number of issues in your code:
Your Presenter has a reference cycle with its interactors
You need to call some init method that eventually calls [super init] in your Presenter's initWithInteractor: method.
As others have pointed out, your methods which begin with init violate Objective-C conventions.
It's hard to tell from what you've posted exactly what your problem is, but I'm very suspicious of how it's structured.
You have a single class (Presenter), which you make two instances of, and pass no parameters other than the Interactor.
How could each instance know to load different "stuff" based on which View controller it received as a parameter?
I still want to understand thoroughly what is going on here, but I did solve the problem by doing the following.
In both Home and Profile, I init the StuffPresenter here:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animation];
[self initPresenter];
}
And when exiting a controllerview I do the following:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
_stuffPresenter = nil;
}
I'm developing an iOS app with latest SDK.
I have created a class that inherits from UIView and I have to do some initialization every time the class is instantiated.
I have to call a method called setUpVars: but I don't know where to send a message to that method:
- (id)initWithFrame:(CGRect)frame;
- (id)initWithCoder:(NSCoder*)aDecoder;
This class can be used with a custom xib, or added to a Storyboard, so I need to be sure that that method will be called on every case.
- (void)setUpVars
{
_preferenceKey = #"";
_preferenceStatus = NO;
_isDown = NO;
}
Where do I have to add [self setUpVars];?
Essentially you will be wanting to cover both cases
- (id)initWithFrame:(CGRect)frame;
{
self = [super initWithFrame:frame];
if (self) {
[self setUpVars];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder;
{
self = [super initWithCoder:aDecoder];
if (self) {
[self setUpVars];
}
return self;
}
I think that you need to send this message from each method, also do not forget about awakeFromNib method.
You can create BOOL variable, something like isAlreadySetup and set it to YES in setUpVars method.
Docs Says
awakeFromNib
Prepares the receiver for service after it has been loaded from an
Interface Builder archive, or nib file.
- (void)awakeFromNib
{
[self setUpVars];
}
If you use Interface Builder to design your interface, initWithFrame: is not called when your view objects are subsequently loaded from the nib file. Instead initWithCoder gets called. So you can initialize your variables in both methods if you prefer a generic way. Works in both case
I tend to think you should call this method from the -(void)viewDidLoad method of the controller in charge
I would like to know if a cell is being dragged (via the grip on the right side when a UITableView is in editing mode). I would like to change the background while it is in that state.
There is no callback unfortunately, but since the default behaviour is to adjust the 'alpha' value of the UITableViewCell you can check for that in '- (void)layoutSubview' of your UITableViewCell subclass.
- (void)layoutSubviews
{
[super layoutSubviews];
if (self.alpha < 0.99) {
// we are most likely being dragged
} else {
// restore for not being dragged
}
}
Note that this is a bit of a hack. I noticed that this doesn't work when the table view's separatorStyle is set to 'UITableViewCellSeparatorStyleNone'.
I don't know if this is possible in the framework. But if it's not, you can try to override touchedMoved method in an UITableViewCell subclass?
You might then be able to also retrieve the state of your tableView (editing or not) in your UItableViewCell's custom subclass.
It is possible, yes!
In ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UIScrollViewDelegate>
{
IBOutlet UITableView * myTable;
IBOutlet UIScrollView * myScroll;
}
In ViewController.m
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
myScroll = myTable;
myScroll.delegate = self;
}
#pragma Mark UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSLog(#"%f",scrollView.contentOffset.y);
}
That is it.
It's possible yes! But you have to keep track on the states yourself in the view controller. Define a member
BOOL tableDrag = NO;
In the method
scrollViewWillBeginDragging
you set tableDrag = YES; And in the method
scrollViewDidEndDragging
you set tableDraw = NO;
Now in the scrollViewDidScroll delegate method you can set the background.
If you have several scrollers then tag the scroll view so that you know which one is dragging.
Hope it helps!
I have a simple request that I have spent much time on (embarrassingly)..
I have sub-classed a UITableView to add some functionality. These new features require things like NSMutableSet which require allocation/initialization.
I have put my object's initialization routine in
- (id)initWithFrame:(CGRect)frame style:(UITableViewStyle)style {
which I understood from the apple docs to correct - but this doesn't get called (determined by break-pointing on the code).
I am using IB, and have dragged a UITableView onto the view, and changed it's class to my new sub-class. There is no UITableViewController.
I have also tried:
- (void)loadView {
- (id)init {
- (id)initWithFrame:(CGRect)frame {
with no success. I would like to have this class work both with IB, and programmatically in the future. Everything works apart from the location of this initialization..
When objects load from a XIB file, they get -(id)initWithCoder:(NSCoder*)coder.
If you create objects from XIBs and programmatically, you'll need to implement both the designated initializer -initWithFrame:style: and -initWithCoder:, doing all your init stuff in each one.
Keeping those two in sync can be a pain, so most folks like to break the init stuff out into a private method, typically called -commonInit.
You can see an example of this in action in some of the Apple sample code: HeadsUpUI.
- (void)commonInit
{
self.userInteractionEnabled = YES;
}
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
[self commonInit];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder])
{
[self commonInit];
}
return self;
}
One common mistake that people make when they're new to Cocoa or Cocoa Touch, is to subclass when they don't actually need to. I've seen many examples of custom windows, tableviews, scrollviews and imageviews that need never have been written.
What functionality are you adding to UITableView? Are you sure that what you want to do can't be accomplished through the delegate methods, or by using a custom cell class?