I am trying to subclass UIScrollView as "ImageScrollView". My class is pretty simple. It has an imageView as one of it's subviews that I want the class to zoom to.
ImageScrollView.h
#import <UIKit/UIKit.h>
#interface ImageScrollView : UIScrollView
#property (nonatomic, strong) UIImageView *imageView;
- (id)initWithFrame:(CGRect)frame andImage:(UIImage *)image;
#end
ImageScrollView.m
#import "ImageScrollView.h"
#interface ImageScrollView()
#property CGFloat scaleWidth;
#property CGFloat scaleHeight;
#end
#implementation ImageScrollView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (id)initWithFrame:(CGRect)frame andImage:(UIImage *)image
{
self = [super init];
if(self)
{
self.frame = frame;
self.imageView = [[UIImageView alloc]initWithImage:image];
[self addSubview:self.imageView];
self.contentSize = self.imageView.bounds.size;
self.scaleWidth = self.frame.size.width / image.size.width;
self.scaleHeight = self.frame.size.height / image.size.height;
CGFloat minScale = MIN(self.scaleWidth, self.scaleHeight);
self.maximumZoomScale = 1.0f;
self.minimumZoomScale = minScale;
[self setZoomScale:minScale];
[self zoomToRect:self.imageView.frame animated:NO];
}
return self;
}
#end
Adding the subclass as a subview of my viewcontroller works just fine except for the scrolling:
#import "ViewController.h"
#import "ImageScrollView.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
ImageScrollView *imageScrollView = [[ImageScrollView alloc]initWithFrame:self.view.bounds andImage:[UIImage imageNamed:#"1.jpg"]];
[self.view addSubview:imageScrollView];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
My first guess is that it has something to do with the delegate not implemented correctly for the subclass. Any suggestions?
Basics for zooming in a UIScrollView
Set scroll view delegate (making sure the class you set as delegate conforms to UIScrollViewDelegate. In your case this is something like self.delegate = self
Set minimumZoomScale or maximumZoomScale (or both)
Implement viewForZoomingInScrollView: to return view to zoom
Figured it out. I had to add the delegate declaration and remove the - (void)zoomToRect:(CGRect)rect animated:(BOOL)animated. Here's the answer in case anyone needs it:
ImageScrollView.h
#import <UIKit/UIKit.h>
#interface ImageScrollView : UIScrollView <UIScrollViewDelegate>
#property (nonatomic, strong) UIImageView *imageView;
- (id)initWithFrame:(CGRect)frame andImage:(UIImage *)image;
#end
ImageScrollView.m
#import "ImageScrollView.h"
#interface ImageScrollView()
#property CGFloat scaleWidth;
#property CGFloat scaleHeight;
#end
#implementation ImageScrollView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (id)initWithFrame:(CGRect)frame andImage:(UIImage *)image
{
self = [super init];
if(self)
{
self.frame = frame;
self.delegate = self;
self.imageView = [[UIImageView alloc]initWithImage:image];
[self addSubview:self.imageView];
self.contentSize = self.imageView.bounds.size;
self.scaleWidth = self.frame.size.width / image.size.width;
self.scaleHeight = self.frame.size.height / image.size.height;
CGFloat minScale = MIN(self.scaleWidth, self.scaleHeight);
self.maximumZoomScale = 1.0f;
self.minimumZoomScale = minScale;
[self setZoomScale:minScale];
}
return self;
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.imageView;
}
Related
My problem with after pinch zoom, when I pinch zoom and after drag image then white space on top and bottom.
My Code as per apple documentation.
https://developer.apple.com/library/content/documentation/WindowsViews/Conceptual/UIScrollView_pg/ZoomZoom/ZoomZoom.html
Sample code
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
scrollView.minimumZoomScale=0.5;
scrollView.maximumZoomScale=6.0;
scrollView.contentSize=CGSizeMake(1280, 960);
scrollView.delegate = self;
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return imageView;
}
Thank you.
At first set your minimum zoom scale to 1.0:
scrollView.minimumZoomScale = 1.0;
Next, set the contentSize for scrollView:
scrollView.contentSize = [UIScreen mainScreen].bounds.size;
And if you don't want to see the extra space when bouncing, add the next line:
scrollView.bounces = NO;
Completely, the method should looks something like this:
- (void)viewDidLoad
{
[super viewDidLoad];
self.scrollView.minimumZoomScale = 1.0;
self.scrollView.maximumZoomScale = 6.0;
self.scrollView.bounces = NO;
self.scrollView.contentSize = [UIScreen mainScreen].bounds.size;
self.scrollView.delegate = self;
}
Hope it will help.
This is how I do it. If you image fits perfectly into the view, there will be no white space when you zoom and pan.
If your image does not fit or scale properly, there will be white space on the sides OR the top.
First make sure your image is the correct size. Next you need to add minimumZoomScale and maximumZoomScale to the scrollView. The rest will work after that. You can use auto-layout or frames. Up to you.
This example uses auto-layout:
#import "ViewController.h"
#interface AutoLayoutScrollView : UIScrollView
#property (nonatomic, weak) UIView *contentView;
#end
#implementation AutoLayoutScrollView
- (instancetype)init {
if (self = [super init]) {
UIView *view = [[UIView alloc] init];
self.contentView = view;
[self addSubview:view];
[self.contentView.leftAnchor constraintEqualToAnchor:self.leftAnchor].active = YES;
[self.contentView.rightAnchor constraintEqualToAnchor:self.rightAnchor].active = YES;
[self.contentView.topAnchor constraintEqualToAnchor:self.topAnchor].active = YES;
[self.contentView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES;
[self.contentView setTranslatesAutoresizingMaskIntoConstraints:NO];
}
return self;
}
- (void)didMoveToSuperview {
[super didMoveToSuperview];
UIView *parent = self.superview;
if (parent) {
[self.leftAnchor constraintEqualToAnchor:parent.leftAnchor].active = YES;
[self.rightAnchor constraintEqualToAnchor:parent.rightAnchor].active = YES;
[self.topAnchor constraintEqualToAnchor:parent.topAnchor].active = YES;
[self.bottomAnchor constraintEqualToAnchor:parent.bottomAnchor].active = YES;
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
}
}
#end
#interface ViewController () <UIScrollViewDelegate>
#property (nonatomic, strong) AutoLayoutScrollView *scrollView;
#property (nonatomic, strong) UIImageView *imageView;
#property (nonatomic, strong) UIPanGestureRecognizer *panGesture;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.scrollView = [[AutoLayoutScrollView alloc] init];
[self.view addSubview:self.scrollView];
[self.scrollView.contentView.widthAnchor constraintEqualToAnchor:self.view.widthAnchor].active = YES;
[self.scrollView.contentView.heightAnchor constraintEqualToAnchor:self.view.heightAnchor].active = YES;
self.imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Image"]];
[self.imageView setContentMode:UIViewContentModeScaleAspectFit];
[self.scrollView.contentView addSubview:self.imageView];
[self.imageView.leftAnchor constraintEqualToAnchor:self.scrollView.contentView.leftAnchor].active = YES;
[self.imageView.rightAnchor constraintEqualToAnchor:self.scrollView.contentView.rightAnchor].active = YES;
[self.imageView.topAnchor constraintEqualToAnchor:self.scrollView.contentView.topAnchor].active = YES;
[self.imageView.bottomAnchor constraintEqualToAnchor:self.scrollView.contentView.bottomAnchor].active = YES;
[self.imageView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.scrollView setMinimumZoomScale:1.0];
[self.scrollView setMaximumZoomScale:3.0];
[self.scrollView setZoomScale:1.0f animated:YES];
[self.scrollView setDelegate:self];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return self.imageView;
}
#end
I have an UICollectionView which uses - (void)scrollViewDidScroll:(UIScrollView *)scrollViewto determine when the user has scrolled to the bottom.
The owner of my UICollectionView has a method which sends a command to a server and asks for more data.
What I want to do is to perform a selector on the owner (my viewcontroller) from my UICollectionView, and specifically from within scrollViewDidScroll.
I try to do it using the following code:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGFloat offsetY = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height;
if (offsetY > contentHeight - scrollView.frame.size.height)
{
[[self superView] performSelector:#selector(onScrolledToBottom) withObject:nil];
}
}
Note: the selector onScrolledToBottom is a SEL property of my UICollectionView.
The error I am getting says:
-[UIView onScrolledToBottom]: unrecognized selector sent to instance 0x7fc641e76e00
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIView onScrolledToBottom]: unrecognized selector sent to instance 0x7fc641e76e00'
EDIT
I've stripped my code down to fit this question.
I have the followin in my ViewController.m
....
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self initCollectionView];
}
....
- (void) getMoreInfo{
NSLog(#"Getting more info");
}
- (void) initCollectionView{
UICollectionViewFlowLayout* flowLayout = [[UICollectionViewFlowLayout alloc]init];
flowLayout.itemSize = CGSizeMake(50.0, 60.0);
flowLayout.sectionInset = UIEdgeInsetsMake(20, 0, 20, 0);
[flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
flowLayout.headerReferenceSize = CGSizeMake(320.f, 30.f);
myCollectionView *mVC = [myCollectionView alloc] init: self.view: flowLayout: #selector(getMoreInfo)];
}
And for my myCollectionView the .hand .mfiles look as following:
#import <UIKit/UIKit.h>
#interface myCollectionView : UICollectionView<UIScrollViewDelegate, UICollectionViewDelegate, UICollectionViewDataSource>{
UIView *parentView;
SEL onScrolledToBottom;
}
#property UIView *parentView;
#property SEL onScrolledToBottom;
- (id)init: (UIView*) parent: (UICollectionViewLayout *)layout: (SEL)onScrolledToBottomSEL;
#end
and
#import "myCollectionView.h"
#implementation myCollectionView
#synthesize parentView = parentView;
#synthesize onScrolledToBottom = onScrollToBotton;
- (id)init: (UIView*) parent: (UICollectionViewLayout *)layout: (SEL)onScrolledToBottomSEL{
self = [super initWithFrame: CGRectMake(0.0f, 0.0f, parent.frame.size.width, parent.frame.size.height) collectionViewLayout: layout];
if (self) {
parentView = parent;
self.delegate = self;
self.dataSource = self;
onScrolledToBottom = onScrolledToBottomSEL;
}
return self;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGFloat offsetY = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height;
if (offsetY > contentHeight - scrollView.frame.size.height)
{
[parentView performSelector:#selector(onScrolledToBottom) withObject:nil];
}
}
You're getting the superview instead of the parent controller, that's one. Secondly, you shouldn't try to do it this way. Instead use a protocol and pass your controller as a delegate to your custom collectionview.
#protocol MyCollectionViewScrollProtocol <NSObject>
- (void)scrolledToBottom;
#end
Then in your ViewController implement this protocol and in your CollectionView
create a weak property delegate like this:
#property(weak, nonatomic) id<MyCollectionViewScrollProtocol> delegate;
Then you'll be able to call it
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGFloat offsetY = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height;
if (offsetY > contentHeight - scrollView.frame.size.height)
{
[self.delegate scrolledToBottom];
}
}
and don't forget to set the delegate property from the controller.
Edit by OP:
The controller should contain the following code in the .hfile:
#import <UIKit/UIKit.h>
#protocol ViewControllerProtocol <NSObject>
- (void) Pong;
#end
#interface ViewController : UIViewController <ViewControllerProtocol>
#end
and the following code somewhere in the .m file:
#import "ViewController.h"
#import UIKit;
#import "myUnit.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
myUnit mU = [[myUnit alloc] init];
mU.linkToController = self;
[mU Ping];
}
......
- (void) Pong{
NSLog(#"Pong");
}
......
The unit that wants to access the controller needs to have the following code in the .hfile:
#import <Foundation/Foundation.h>
#import "ViewController.h"
#interface myUnit : NSObject {
}
#property (weak, nonatomic) id <ViewControllerProtocol> linkToController;
- (void)Ping;
#end
And the unit that wants to access the controller needs to have the following code in the .mfile:
#import "myUnit.h"
#import "ViewController.h"
#implementation myUnit
- (void) Ping {
NSLog(#"Ping");
[self.linkToController Pong];
}
#end
The collectionView.parentView will be ViewController.view, which is totally a UIView, doesn't have a method called onScrolledToBottom.
Here is a approach if I right about what you want, but IT'S REALLY REALLY A BAD WAY to do this.
in ViewController.m
....
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self initCollectionView];
}
....
- (void) getMoreInfo{
NSLog(#"Getting more info");
}
- (void) initCollectionView{
UICollectionViewFlowLayout* flowLayout = [[UICollectionViewFlowLayout alloc]init];
flowLayout.itemSize = CGSizeMake(50.0, 60.0);
flowLayout.sectionInset = UIEdgeInsetsMake(20, 0, 20, 0);
[flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
flowLayout.headerReferenceSize = CGSizeMake(320.f, 30.f);
myCollectionView *mVC = [myCollectionView alloc] initWithParentViewController:self layout:flowLayout selector:#selector(getMoreInfo)];
}
in myCollectionView.h
#import <UIKit/UIKit.h>
#interface myCollectionView : UICollectionView<UIScrollViewDelegate, UICollectionViewDelegate, UICollectionViewDataSource>{
UIViewController *_parentViewController;
SEL _onScrolledToBottom;
}
#property(weak) UIViewController *parentViewController;
#property SEL onScrolledToBottom;
- (id)initWithParentViewController:(UIView *)parentViewController layout:(UICollectionViewLayout *)layout selector:(SEL)onScrolledToBottomSEL;
#end
in myCollectionView.m
#import "myCollectionView.h"
#implementation myCollectionView
#synthesize parentViewController = _parentViewController;
#synthesize onScrolledToBottom = _onScrollToBotton;
- (id)initWithParentViewController:(UIView *)parentViewController layout:(UICollectionViewLayout *)layout selector:(SEL)onScrolledToBottomSEL{
self = [super initWithFrame: CGRectMake(0.0f, 0.0f, parent.frame.size.width, parent.frame.size.height) collectionViewLayout: layout];
if (self) {
_parentViewController = parentViewController;
self.delegate = self;
self.dataSource = self;
_onScrolledToBottom = onScrolledToBottomSEL;
}
return self;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGFloat offsetY = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height;
if (offsetY > contentHeight - scrollView.frame.size.height)
{
[_parentViewController performSelector:#selector(onScrolledToBottom) withObject:nil];
}
}
WARNING:
In MVC there should be a Controller to implement
the protocol, not a view.
Class name's first character must be capitalized, you should use MyCollectionView instead of myCollectionView as a class name.
When use property, the LLVM compiler will automatically generate a member variable, no need to declare it again.
In this case use protocol-delegate is the best way.
Your "myCollectionView" should have a delegate (you will have to create a protocol for that) with some method along the line:
- (void)myCollectionViewDidScrollToBottom:(myCollectionView *)collectionView
By convention view will pass itself to this method (just like any tableView delegate methods). Then in the initCollectionView after myCollectionView *mVC = [myCollectionView alloc] init... you will have to set this delegate to the current view controller and implement that method.
mVC.delegate = self;
...
some other place in code
...
- (void)myCollectionViewDidScrollToBottom:(myCollectionView *)collectionView {
//do what's need to be done ;)
}
Now your "myCollectionView" does not care where it's just and by "who". Anyone who would like to use it can implement this method and be a delegate for that view with any custom logic. That is in this method you would call any required methods and any other things.
How this works:
View when scrolls down notifies it's delegate about this event
Delegate handles all the logic what's need to be done
:)
First I want to say thank you to all of you.
I read through everything you guys wrote and I was inspired.
Especially Stanislav Ageev's answer where he said
You're getting the superview instead of the parent controller...
.
I tried to pass the controller itself as a UIViewController instead of view of the controller.
And I also removed the #selector() from within the scrollViewDidScroll and passed the selector directly.
EDIT I missed zy.lius answer. Credit goes to him/her for posting an answer which basically explains what I've done here even though I figured this out on my own. Sorry.
My code now looks like this (.h/.m):
Controller
....
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self initCollectionView];
}
....
- (void) getMoreInfo{
NSLog(#"Getting more info");
}
- (void) initCollectionView{
UICollectionViewFlowLayout* flowLayout = [[UICollectionViewFlowLayout alloc]init];
flowLayout.itemSize = CGSizeMake(50.0, 60.0);
flowLayout.sectionInset = UIEdgeInsetsMake(20, 0, 20, 0);
[flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
flowLayout.headerReferenceSize = CGSizeMake(320.f, 30.f);
myCollectionView *mVC = [myCollectionView alloc] init: self: flowLayout: #selector(getMoreInfo)];
}
myCollectionView.h file:
#import <UIKit/UIKit.h>
#interface myCollectionView : UICollectionView<UIScrollViewDelegate, UICollectionViewDelegate, UICollectionViewDataSource>{
UICollectionView *parentController;
SEL onScrolledToBottom;
}
#property UICollectionView *parentController;
#property SEL onScrolledToBottom;
- (id)init: (UICollectionView*) parent: (UICollectionViewLayout *)layout: (SEL)onScrolledToBottomSEL;
#end
and the .m file:
#import "myCollectionView.h"
#implementation myCollectionView
#synthesize parentController = parentController;
#synthesize onScrolledToBottom = onScrollToBotton;
- (id)init: (UIView*) parent: (UICollectionViewLayout *)layout: (SEL)onScrolledToBottomSEL{
self = [super initWithFrame: CGRectMake(0.0f, 0.0f, parent.view.frame.size.width, parent.view.frame.size.height) collectionViewLayout: layout];
if (self) {
parentController = parent;
self.delegate = self;
self.dataSource = self;
onScrolledToBottom = onScrolledToBottomSEL;
}
return self;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGFloat offsetY = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height;
if (offsetY > contentHeight - scrollView.frame.size.height)
{
[parentController performSelector:#onScrolledToBottom withObject:nil];
}
}
This works perfectly fine and this could be used to communicate with the main controller in various ways.
And it is very simple.
Thanks again for inspiring me to find the most efficient solution.
I want to show an image using a UIImageView inside a UIScrollview. The image needs to fit on the screen while keeping the aspect ratio.
This all works, except that, when I zoom first, I can scroll below the image. The height of area is about 64.
This is how it looks after scrolling (White is the background color of the UIImageView and red is the background color of the UIScrollView):
This is the code in my ImageViewController:
#import "ImageViewController.h"
#interface ImageViewController () <UIScrollViewDelegate>
#property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
#property (nonatomic, strong) UIImageView *imageView;
#property (nonatomic, strong) UIImage *image;
#end
#implementation ImageViewController
- (void)viewDidLoad
{
NSLog(#"viewDidLoad");
[super viewDidLoad];
_scrollView.minimumZoomScale = 1.0 ;
_scrollView.maximumZoomScale = _imageView.image.size.width / _scrollView.frame.size.width;
_scrollView.zoomScale = 1.0;
_scrollView.backgroundColor = [UIColor redColor];
_scrollView.delegate = self;
_imageView.backgroundColor = [UIColor whiteColor];
[_scrollView addSubview:_imageView];
}
- (void)viewDidLayoutSubviews
{
self.imageView.frame = self.scrollView.bounds;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.imageView setContentMode:UIViewContentModeScaleAspectFit];
}
- (UIImageView *)imageView
{
if (!_imageView) _imageView = [[UIImageView alloc] init];
return _imageView;
}
- (UIImage *)image
{
NSLog(#"image");
return self.imageView.image;
}
- (void)setImage:(UIImage *)image
{
NSLog(#"setImage");
self.imageView.image = image;
}
#pragma mark - UIScrollViewDelegate
// mandatory zooming method in UIScrollViewDelegate protocol
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.imageView;
}
-(void)setImageURL:(NSURL *)imageURL
{
NSLog(#"setImageURL");
_imageURL = imageURL;
self.image = [UIImage imageWithContentsOfFile:[imageURL path]];
}
#end
This are the properties of my UIScrollView:
How can I fix this?
I got it working. This is my code:
#import "ImageViewController.h"
#interface ImageViewController () <UIScrollViewDelegate>
#property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
#property (nonatomic, strong) UIImageView *imageView;
#property (nonatomic, strong) UIImage *image;
#end
#implementation ImageViewController
- (void)viewDidLoad
{
NSLog(#"viewDidLoad");
[super viewDidLoad];
self.scrollView.contentSize = self.scrollView.frame.size;
self.scrollView.delegate = self;
CGRect rect = CGRectZero;
rect.size = self.image.size;
self.scrollView.minimumZoomScale = 0.5;
self.scrollView.maximumZoomScale = 2.5;
self.scrollView.zoomScale = 1.0;
self.imageView = [[UIImageView alloc] initWithFrame:rect];
self.imageView.image = self.image;
[self.scrollView addSubview:self.imageView];
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
}
- (IBAction)tappedAction:(id)sender {
UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:#[self.image] applicationActivities:nil];
[self presentViewController:activityVC animated:YES completion:nil];
}
- (void)viewDidLayoutSubviews
{
self.imageView.frame = self.scrollView.bounds;
}
#pragma mark - UIScrollViewDelegate
// mandatory zooming method in UIScrollViewDelegate protocol
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.imageView;
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
UIView *subView = [scrollView.subviews objectAtIndex:0];
CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);
subView.center = CGPointMake(scrollView.contentSize.width * 0.5 + offsetX,
scrollView.contentSize.height * 0.5 + offsetY);
}
-(void)setImageURL:(NSURL *)imageURL
{
NSLog(#"setImageURL");
_imageURL = imageURL;
self.image = [UIImage imageWithContentsOfFile:[imageURL path]];
}
#end
The code in scrollViewDidZoom: is used to keep the image centered.
I've setup a delegate to take the old and new position of a gauge. However, I can't seem to get a response from the delegate. I could be missing something but I've done a bunch of research and it seems like everything is in order.
MeterView.h
#import <UIKit/UIKit.h>
#import "KTOneFingerRotationGestureRecognizer.h"
#protocol MeterViewDelegate;
#interface MeterView : UIView{
IBOutlet UIImageView *gauge;
IBOutlet UIImageView *needle;
float rotation, oldPos, newPos;
KTOneFingerRotationGestureRecognizer *rgest;
}
#property (nonatomic, weak) id<MeterViewDelegate> delegate;
- (IBAction)handleMovedNeedle;
#end
#protocol MeterViewDelegate <NSObject>
- (void)meterView:(MeterView*)view OldValue:(float)oldval NewValue:(float)newval;
#end
MeterView.m
#import "MeterView.h"
#import <QuartzCore/QuartzCore.h>
#implementation MeterView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
-(void)handleMovedNeedle{
if ([self.delegate respondsToSelector:#selector(meterView:OldValue:NewValue:)]) {
[self.delegate meterView:self OldValue:oldPos NewValue:newPos];
}
else{
NSLog(#"Delegate call FAIL, needle moved from %f, to %f", oldPos,newPos);
}
}
-(void)awakeFromNib{
gauge = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"gauge.png"]];
needle = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"needle.png"]];
needle.frame = CGRectMake(0,gauge.frame.size.height-(needle.frame.size.height/2), gauge.frame.size.width, needle.frame.size.height);
[self addSubview:gauge];
[self addSubview:needle];
rotation = 0;
rgest = [[KTOneFingerRotationGestureRecognizer alloc] initWithTarget:self action:#selector(rotate:)];
rgest.center = CGPointMake(CGRectGetMidX([needle bounds]) + needle.frame.origin.x, CGRectGetMidY([needle bounds]) + needle.frame.origin.y);
[self addGestureRecognizer:rgest];
}
- (void)rotate:(UIRotationGestureRecognizer *)recognizer {
switch (recognizer.state) {
case UIGestureRecognizerStateBegan: {
oldPos = ([(NSNumber *)[needle.layer valueForKeyPath:#"transform.rotation.z"] floatValue]/3.14)*100;
}
break;
case UIGestureRecognizerStateChanged: {
CGFloat angle = [(NSNumber *)[needle.layer valueForKeyPath:#"transform.rotation.z"] floatValue];
angle += [recognizer rotation];
if (angle >= 0 && angle <= M_PI) {
[needle setTransform:CGAffineTransformRotate([needle transform], [recognizer rotation])];
rotation += [recognizer rotation];
}
}
break;
case UIGestureRecognizerStateEnded: {
newPos = ([(NSNumber *)[needle.layer valueForKeyPath:#"transform.rotation.z"] floatValue]/3.14)*100;
[self handleMovedNeedle];
}
break;
default:
break;
}
}
#end
ViewController.h
#import <UIKit/UIKit.h>
#import "MeterView.h"
#import "KTOneFingerRotationGestureRecognizer.h"
#interface ViewController : UIViewController<MeterViewDelegate>{
IBOutlet MeterView *secondMeter;
IBOutlet MeterView *thirdMeter;
}
#end
ViewController.m
#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>
#define DEG2RAD(degrees) (degrees * 0.01745327) // degrees * pi over 180
#define RAD2DEG(radians) (radians * 57.2957795) // radians * 180 over pi
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
secondMeter = [[MeterView alloc]initWithFrame:[[UIScreen mainScreen] bounds]];
secondMeter.delegate = self;
thirdMeter = [[MeterView alloc]initWithFrame:[[UIScreen mainScreen] bounds]];
thirdMeter.delegate =self;
}
-(void)meterView:(MeterView *)view OldValue:(float)oldval NewValue:(float)newval{
NSLog(#"Delegate call SUCCESS, need moved from %f, to %f", oldval,newval);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
This code:
secondMeter = [[MeterView alloc]initWithFrame:[[UIScreen mainScreen] bounds]];
secondMeter.delegate = self;
thirdMeter = [[MeterView alloc]initWithFrame:[[UIScreen mainScreen] bounds]];
thirdMeter.delegate =self;
creates 2 instances of your view, but it doesn't add them to a view, so they will never be seen.
So, you probably have some other view instances that are on view and don't have delegates, and these views which have delegates but aren't on display. Presumably from an XIB / storyboard as you have outlets.
Connect the views that are on display to the delegate using the outlets rather than creating new instances.
If you make the delegate property in the view an IBOutlet itself then you can connect the view controller as the delegate in the XIB / storyboard and you don't need to worry about the delegate in code at all...
I'm building a graphing app, and I have a view and a view controller, which acts as a delegate for the view (to retrieve information). While I haven't started the actual drawing yet, I am currently trying to store values in a dictionary; however, I have certain NSLogs placed methodically across the view controller and I noticed that the delegate methods I call from the view don't get called at all. For example, I call my scaleForGraph function, but it does not execute. Being new to this, I'm not sure if there's something I'm missing. FYI: I have no errors, it compiles and executes. I've tried to slim the code down as much as possible. Thank you for your help!
Here's the .h for my view, where I define the protocol:
// GraphView.h
#import <UIKit/UIKit.h>
#class GraphView;
#protocol GraphViewDelegate <NSObject>
- (float)scaleForGraph:(GraphView *)requestor;
-(NSMutableDictionary*) valuesForGraph:(GraphView *)requestor withWidth:(float) width;
#end
#interface GraphView : UIView
#property (nonatomic) id <GraphViewDelegate> delegate;
#property (nonatomic) id expressionCopy;
#end
And here's the .m:
#import "GraphView.h"
#implementation GraphView
#synthesize expressionCopy = _expressionCopy;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.contentMode = UIViewContentModeRedraw;
}
return self;
}
- (void)awakeFromNib
{
self.contentMode = UIViewContentModeRedraw;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
CGContextRef c = UIGraphicsGetCurrentContext();
CGRect screenBound = [[UIScreen mainScreen] bounds];
CGSize screenSize = screenBound.size;
CGFloat width = screenSize.width;
CGFloat height = screenSize.height;
float scale = [self.delegate scaleForGraph:self];
NSLog([NSString stringWithFormat:#"My Scale: %f",scale]); //always returns 0
NSMutableDictionary *graphValues = [self.delegate valuesForGraph:self withWidth:width];
}
#end
And here's my view controller .h:
#import <UIKit/UIKit.h>
#import "GraphView.h"
#interface GraphingViewController : UIViewController <GraphViewDelegate>
#property (weak, nonatomic) IBOutlet GraphView *graphView;
#property (strong, nonatomic) IBOutlet UIStepper *stepper;
- (IBAction) changedScale:(UIStepper *)stepper;
#property (nonatomic) int scale;
#property (nonatomic) id expressionCopy;
#end
And here's the .m for the controller:
#import "GraphingViewController.h"
#interface GraphingViewController ()
#end
#implementation GraphingViewController
#synthesize expressionCopy = _expressionCopy;
- (void)updateUI
{
self.stepper.value = self.scale;
[self.graphView setNeedsDisplay];
}
- (void)setScale:(int)scale
{
if (scale < 0) scale = 0;
if (scale > 100) scale = 100;
_scale = scale;
[self updateUI];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(IBAction)changedScale:(UIStepper *)stepper {
self.scale = stepper.value; //this function works fine, but is not delegate/does not get called by view
}
-(float) scaleForGraph:(GraphView *)requestor {
NSLog(#"HI"); //never gets here
}
-(NSMutableDictionary*) valuesForGraph:(GraphView *)requestor withWidth:(float) width {
NSLog(#"Hello2"); //never gets here
}
return xyVals;
}
#end
Nowhere in the code you've posted do you tell your GraphView that the GraphingViewController is it's delegate. So, you are sending a message to nil.
You'll want to do something like:
self.graphView.delegate = self;
In your GraphingViewController setup code.
Make your controller actual delegate of GraphView. You can do it in interface builder by Ctrl-dragging from GraphView to the object (orange circle in the bottom) and than choose "delegate"