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...
Related
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.
Following this tutorial on how to display XML latitude and longitude data as pins on iOS map kit:
http://highoncoding.com/Articles/805_Consuming_XML_Feed_and_Displaying_Public_Information_on_the_MapView_Control.aspx
The sample code provided compiles correctly and displays pins all across the united states. However when I tried to "port" the .xib into my app It pulls up my Mapview and the current users location, but it won't drop any pins/parse the data?
I'm on Day 2 now, its a bit discouraging.
Heres my .m and .h
EleventhViewController.h
// SlideMenu
//
// Created by Kyle Begeman on 1/13/13.
// Copyright (c) 2013 Indee Box LLC. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#import <CoreLocation/CoreLocation.h>
#import "ECSlidingViewController.h"
#import "MenuViewController.h"
//#interface EleventhViewController : NSObject <UIApplicationDelegate,MKMapViewDelegate,CLLocationManagerDelegate> {
#interface EleventhViewController : UIViewController <MKMapViewDelegate,CLLocationManagerDelegate,UIApplicationDelegate>
{
IBOutlet MKMapView *mapView;
NSMutableArray *greenCities;
CLLocationManager *locationManager;
}
//-(void) MKMapViewDelegate;
//-(void) CLLocationManagerDelegate;
//#property (nonatomic, weak) id<UIApplicationDelegate> delegate;
//#property (nonatomic, weak) id<MKMapViewDelegate> delegate;
//#property (nonatomic, weak) id<CLLocationManagerDelegate> delegate;
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic,retain) IBOutlet MKMapView *mapView;
#property (nonatomic,retain) NSMutableArray *greenCities;
#property (strong, nonatomic) UIButton *menuBtn;
#property (strong, nonatomic) UIButton *searchBtn;
#end
Heres the .m
//
// EleventhViewController.m
// SlideMenu
//
// Created by Kyle Begeman on 1/13/13.
// Copyright (c) 2013 Indee Box LLC. All rights reserved.
//
#import "EleventhViewController.h"
#import "ECSlidingViewController.h"
#import "MenuViewController.h"
#import "GreenCitiesAppDelegate.h"
#import "GreenCitiesService.h"
#import "GreenCityAnnotation.h"
#import "GreenCityAnnotationView.h"
#import "GreenCity.h"
#interface EleventhViewController ()
//#interface EleventhViewController : UIViewController <MKMapViewDelegate>
#end
#implementation EleventhViewController
#synthesize window=_window,mapView,greenCities;
#synthesize menuBtn;
#synthesize searchBtn;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.mapView.delegate = self;
locationManager = [[CLLocationManager alloc] init];
[locationManager setDelegate:self];
[locationManager setDistanceFilter:kCLDistanceFilterNone];
[locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
[self.mapView setShowsUserLocation:YES];
GreenCitiesService *greenService = [[GreenCitiesService alloc] init];
self.greenCities = [greenService getGreenCities];
[self.window makeKeyAndVisible];
return YES;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.window.layer.shadowOpacity = 0.75f;
self.window.layer.shadowRadius = 10.0f;
self.window.layer.shadowColor = [UIColor blackColor].CGColor;
if (![self.slidingViewController.underLeftViewController isKindOfClass:[MenuViewController class]]) {
self.slidingViewController.underLeftViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"Menu"];
}
self.menuBtn = [UIButton buttonWithType:UIButtonTypeCustom];
menuBtn.frame = CGRectMake(9, 23, 40, 30);
[menuBtn setBackgroundImage:[UIImage imageNamed:#"menuButton.png"] forState:UIControlStateNormal];
[menuBtn addTarget:self action:#selector(revealMenu:) forControlEvents:UIControlEventTouchUpInside];
[self.window addSubview:self.menuBtn];
//Top Main Menu Search Button
self.searchBtn = [UIButton buttonWithType:UIButtonTypeCustom];
searchBtn.frame = CGRectMake(275, 25, 40, 30);
[searchBtn setBackgroundImage:[UIImage imageNamed:#"searchButton.png"] forState:UIControlStateNormal];
[searchBtn addTarget:self action:#selector(revealMenu:) forControlEvents:UIControlEventTouchUpInside];
[self.window addSubview:self.searchBtn];
}
- (void)mapView:(MKMapView *)mv didUpdateUserLocation:(MKUserLocation *)userLocation
{
NSLog(#"didUpdateUserLocation fired!");
CLLocationCoordinate2D maxCoord = {-90.0f,-180.0f};
CLLocationCoordinate2D minCoord = {90.0f, 180.0f};
for(int i = 0; i<=[self.greenCities count] - 1;i++)
{
GreenCity *gCity = (GreenCity *) [self.greenCities objectAtIndex:i];
CLLocationCoordinate2D newCoord = { gCity.latitude, gCity.longitude };
if(gCity.longitude > maxCoord.longitude)
{
maxCoord.longitude = gCity.longitude;
}
if(gCity.latitude > maxCoord.latitude)
{
maxCoord.latitude = gCity.latitude;
}
if(gCity.longitude < minCoord.longitude)
{
minCoord.longitude = gCity.longitude;
}
if(gCity.latitude < minCoord.latitude)
{
minCoord.latitude = gCity.latitude;
}
GreenCityAnnotation *annotation = [[GreenCityAnnotation alloc] initWithCoordinate:newCoord title:gCity.name subTitle:gCity.rank];
[mv addAnnotation:annotation];
// [annotation release];
}
MKCoordinateRegion region = {{0.0f, 0.0f}, {0.0f, 0.0f}};
region.center.longitude = (minCoord.longitude + maxCoord.longitude) / 2.0;
region.center.latitude = (minCoord.latitude + maxCoord.latitude) / 2.0;
// calculate the span
region.span.longitudeDelta = maxCoord.longitude - minCoord.longitude;
region.span.latitudeDelta = maxCoord.latitude - minCoord.latitude;
[self.mapView setRegion:region animated:YES];
}
- (void)mapView:(MKMapView *)mv didAddAnnotationViews:(NSArray *)views
{
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)revealMenu:(id)sender
{
[self.slidingViewController anchorTopViewTo:ECRight];
}
#end
My Project is pretty simple, uses a lot of free source code so feel free to download what I've made up to this point, I'm pretty sure it would make a good template/starting base for a lot of you:
https://www.dropbox.com/s/8xxx08zyqpr9i8v/CDF.zip
The method didFinishLauching with options should only be used in the app delegate. You need to move the code from here into viewDidLoad and delete the didFinishLaunching method. Also delete the reference you have to the app delegate from the storyboard xib.
You really want to then set breakpoints in the app and work out where the data load is going wrong.
That fixed it! I moved every piece of executed code under the viewDidLoad! I feel like a noob (which I am) I would up vote you but I don't have enough rep yet. However I keep my connections to the app delegate I imported from the greencities .zip source. Thanks so much. This has however created a new bug in that I can't access my slider menu...fix one problem get another...I really appreciate the direction
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;
}
I'm making a news reading app. I have a ArticleDetailPagingVC which functions as a paging controller. This has a UIScrollView with multiple ArticleDetailViewController's.
Inside the ArticleDetailViewController is a UIWebView which handles the articleText.
After changing some code I got a EXC_BAD_ACCESS when trying to inject a HTML string in the UIWebView. I eventually ended up looking for NSZombie's, which I found:
As seen in the screenshot the NSZombie points to setting the frame of the ArticleDetailViewController, which is not correct in my opinion.
If I comment out the line of code which injects the HTMLString to my UIWebView, the view is shown as it should, without any data in the UIWebView.
The WebView is created as an IBOutlet:
#property (nonatomic) IBOutlet UIWebView *webView;
Delegate is set to self (ArticleDetailViewController)
Also, its crashing before any of the UIWebView Delegate Methods are called.
I'm sure the problem is not:
The HTML String (it was working before & if I load a 'Hello world' string its crashing too)
MultiThreading (everything is handled on the mainthread for testing purposes)
I have no weak properties
I have 0 autoreleasepool's / CFRelease(object) in my code
I have no idea what could have been released too soon to create the crash
So my question is, how do you debug such a NSZombie? Or any other pointers are much appreciated.
PagingVC.h
#import <UIKit/UIKit.h>
#import "DDScrollViewController.h"
#import "ThumbArticle.h"
#import "NewsArticle.h"
#import "MBProgressHUD.h"
#import "DDScrollViewDelegate.h"
#interface ArticleDetailPagingVC : UIViewController <UIScrollViewDelegate,MBProgressHUDDelegate>
//View
#property (nonatomic) IBOutlet UIScrollView *scrollView;
//Data
#property (nonatomic) ThumbArticle *selectedThumbArticle;
#property (nonatomic) NewsArticle *selectedNewsArticle;
#property (nonatomic) int indexOfSelectedArticle;
#property (nonatomic) NSMutableArray *dataList;
#property (nonatomic) NSInteger selectedPage;
#property (nonatomic) BOOL dataSet;
#property (nonatomic) MBProgressHUD *mbProcess;
-(id)initWithDataList:(NSMutableArray*)dataList;
#end
PagingVC.m
#import "ArticleDetailPagingVC.h"
#import "ArticleDetailViewController.h"
#interface ArticleDetailPagingVC ()
-(void)setupView;
-(void)setupViewWithThumbArticles;
-(void)setupViewWithNewsArticles;
#end
#implementation ArticleDetailPagingVC
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
self.dataSet = NO;
}
return self;
}
-(id)initWithDataList:(NSMutableArray*)dataList
{
self = [super init];
if (self) {
self.dataSet = NO;
self.dataList = [NSMutableArray arrayWithArray:dataList];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (self.selectedThumbArticle) {
self.indexOfSelectedArticle = [self.dataList indexOfObject:self.selectedThumbArticle];
} else if (self.selectedNewsArticle) {
self.indexOfSelectedArticle = [self.dataList indexOfObject:self.selectedNewsArticle];
}
}
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self setupView];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark -
#pragma mark Custom Methods
-(void)setupView
{
if (self.dataList.count > 0) {
id object = [self.dataList objectAtIndex:0];
if ([object isKindOfClass:[NewsArticle class]]) {
} else if ([object isKindOfClass:[ThumbArticle class]]) {
ArticleDetailViewController *articleDetailVC = [[ArticleDetailViewController alloc] init];
articleDetailVC.selectedThumbArticle = [self.dataList objectAtIndex:self.indexOfSelectedArticle];
articleDetailVC.view.frame = CGRectMake(self.indexOfSelectedArticle * self.scrollView.frame.size.width, 0, self.scrollView.frame.size.width, self.scrollView.frame.size.height);
[self.scrollView addSubview:articleDetailVC.view];
//[articleDetailVC layoutViewWithThumbArticle:[self.dataList objectAtIndex:self.indexOfSelectedArticle]];
}
self.scrollView.contentSize = CGSizeMake(self.dataList.count * self.scrollView.frame.size.width, self.scrollView.frame.size.height);
[self.scrollView setContentOffset:CGPointMake(self.indexOfSelectedArticle * self.scrollView.frame.size.width, 0) animated:NO];
self.dataSet = YES;
}
}
-(void)setupViewWithThumbArticles
{
//Set the selected article first
/*
ArticleDetailViewController *articleDetailVC = [[ArticleDetailViewController alloc] init];
dispatch_async(dispatch_get_main_queue(), ^{
articleDetailVC.view.frame = CGRectMake(indexOfSelectedArticle * self.scrollView.frame.size.width, 0, self.scrollView.frame.size.width, self.scrollView.frame.size.height);
[self.scrollView addSubview:articleDetailVC.view];
});
[self.viewControllers replaceObjectAtIndex:indexOfSelectedArticle withObject:articleDetailVC];
[articleDetailVC layoutViewWithThumbArticle:[self.dataList objectAtIndex:indexOfSelectedArticle]];
//Then loop through the rest to add them to the scrollview
*/
int i = 0;
for (ThumbArticle *article in self.dataList) {
if (i != self.indexOfSelectedArticle) {
ArticleDetailViewController *articleDetailVC = [[ArticleDetailViewController alloc] init];
dispatch_async(dispatch_get_main_queue(), ^{
articleDetailVC.view.frame = CGRectMake(i * self.scrollView.frame.size.width, 0, self.scrollView.frame.size.width, self.scrollView.frame.size.height);
[self.scrollView addSubview:articleDetailVC.view];
});
//[self.viewControllers replaceObjectAtIndex:i withObject:articleDetailVC];
}
i++;
}
dispatch_async(dispatch_get_main_queue(), ^{
self.scrollView.contentSize = CGSizeMake(i * self.scrollView.frame.size.width, self.scrollView.frame.size.height);
[self.scrollView setContentOffset:CGPointMake(self.indexOfSelectedArticle * self.scrollView.frame.size.width, 0) animated:NO];
});
}
-(void)setupViewWithNewsArticles
{
int indexOfSelectedArticle = [self.dataList indexOfObject:self.selectedNewsArticle];
ArticleDetailViewController *articleDetailVC = [[ArticleDetailViewController alloc] init];
dispatch_async(dispatch_get_main_queue(), ^{
articleDetailVC.view.frame = CGRectMake(indexOfSelectedArticle * self.scrollView.frame.size.width, 0, self.scrollView.frame.size.width, self.scrollView.frame.size.height);
[self.scrollView addSubview:articleDetailVC.view];
});
//[self.viewControllers replaceObjectAtIndex:indexOfSelectedArticle withObject:articleDetailVC];
[articleDetailVC layoutViewWithNewsArticle:[self.dataList objectAtIndex:indexOfSelectedArticle]];
int i = 0;
for (NewsArticle *article in self.dataList) {
if (i != indexOfSelectedArticle) {
ArticleDetailViewController *articleDetailVC = [[ArticleDetailViewController alloc] init];
dispatch_async(dispatch_get_main_queue(), ^{
articleDetailVC.view.frame = CGRectMake(i * self.scrollView.frame.size.width, 0, self.scrollView.frame.size.width, self.scrollView.frame.size.height);
[self.scrollView addSubview:articleDetailVC.view];
});
//[self.viewControllers replaceObjectAtIndex:i withObject:articleDetailVC];
}
i++;
}
self.scrollView.contentSize = CGSizeMake(i * self.scrollView.frame.size.width, self.scrollView.frame.size.height);
[self.scrollView setContentOffset:CGPointMake(indexOfSelectedArticle * self.scrollView.frame.size.width, 0) animated:NO];
}
#pragma mark -
#pragma mark UIScrollView Delegate Methods
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (fmodf(scrollView.contentOffset.x, scrollView.frame.size.width) == 0) {
if (self.dataSet) {
self.selectedPage = scrollView.contentOffset.x / self.scrollView.frame.size.width;
ArticleDetailViewController *articleDetailVC = [[ArticleDetailViewController alloc] initWithNibName:#"ArticleDetailViewController" bundle:nil];
articleDetailVC.view.frame = CGRectMake(self.selectedPage * self.scrollView.frame.size.width, 0, self.scrollView.frame.size.width, self.scrollView.frame.size.height);
[self.scrollView addSubview:articleDetailVC.view];
[articleDetailVC layoutViewWithThumbArticle:[self.dataList objectAtIndex:self.selectedPage]];
}
}
}
#pragma mark -
#pragma mark MBProgressHUDDelegate methods
- (void)hudWasHidden
{
[self.mbProcess removeFromSuperview];
}
#end
ArticleDetailViewController.h
#import "DDViewController.h"
#import "ThumbArticle.h"
#import "NewsArticle.h"
#import "MBProgressHUD.h"
#import "DDAsyncParser+NewsArticles.h"
#interface ArticleDetailViewController : DDViewController <MBProgressHUDDelegate,UIWebViewDelegate,ParserDelegate>
//View
#property (nonatomic,strong) IBOutlet UIView *contentView;
#property (nonatomic) IBOutlet UIImageView *image;
#property (nonatomic) IBOutlet UILabel *labelCategory;
#property (nonatomic) IBOutlet UILabel *labelImgCaption;
#property (nonatomic) IBOutlet UILabel *labelEdition;
#property (nonatomic) IBOutlet UIWebView *webView;
#property (nonatomic) IBOutlet UIActivityIndicatorView *activity;
#property (nonatomic) NSUInteger textFontSize;
#property (nonatomic) MBProgressHUD *mbProcess;
//Data
#property (nonatomic) ThumbArticle *selectedThumbArticle;
ArticleDetailViewController.m
#import "ArticleDetailViewController.h"
#import "DDUtilities.h"
#import "DDUserDefaults.h"
#import "NewsArticle.h"
#import "DDFeedParser.h"
#interface ArticleDetailViewController ()
#property (nonatomic) NewsArticle *parsedNewsArticle;
-(void)loadData;
-(void)updateImageCaptionLabel;
-(void)populateWebView;
#end
#implementation ArticleDetailViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
[self.view addSubview:self.contentView];
((UIScrollView*)self.view).contentSize = self.contentView.frame.size;
self.dataSet = NO;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (!self.dataSet) {
[[DDAsyncParser sharedInstance] parseArticleWithXMLURL:self.selectedThumbArticle.articleXMLUrl delegate:self];
}
}
- (void)viewWillUnload
{
[self.webView setDelegate:nil];
[self.webView stopLoading];
}
- (void)viewWillDisappear:(BOOL)animated{
[self.webView setDelegate:nil];
[self.webView stopLoading];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark -
#pragma mark Public Methods
-(void)layoutViewWithThumbArticle:(ThumbArticle*)article
{
if (!self.dataSet) {
self.selectedThumbArticle = article;
[[DDAsyncParser sharedInstance] parseArticleWithXMLURL:self.selectedThumbArticle.articleXMLUrl delegate:self];
}
}
-(void)layoutViewWithNewsArticle:(NewsArticle*)article
{
if (!self.dataSet) {
self.parsedNewsArticle = article;
[self loadData];
}
}
#pragma mark -
#pragma mark Private Methods
-(void)loadData
{
self.labelCategory.text = self.parsedNewsArticle.articleCategory;
self.labelCategory.font = kCalibriBold14;
self.labelCategory.textColor = kGrayColor;
self.labelEdition.text = self.parsedNewsArticle.articleEdition;
self.labelEdition.font = kCalibriBold14;
self.labelEdition.textColor = kGrayColor;
[self updateImageCaptionLabel];
[self populateWebView];
self.dataSet = YES;
}
-(void)updateImageCaptionLabel
{
self.labelImgCaption.text = #"";
NSString *imgAuthor = #"";
if (self.parsedNewsArticle.articleImgAuthor.length != 0) {
imgAuthor = [NSString stringWithFormat:#"Foto: %#",self.parsedNewsArticle.articleImgAuthor];
}
NSString *imgCaption = #"";
if (self.parsedNewsArticle.articleImgDescription.length != 0) {
imgCaption = [NSString stringWithFormat:#"%# \n%#",self.parsedNewsArticle.articleImgDescription,imgAuthor];
} else {
imgCaption = imgAuthor;
}
self.labelImgCaption.text = imgCaption;
CGSize maximumLabelSize = CGSizeMake(296,9999);
CGSize expectedLabelSize = [imgCaption sizeWithFont:self.labelImgCaption.font
constrainedToSize:maximumLabelSize
lineBreakMode:self.labelImgCaption.lineBreakMode];
CGRect newFrame = self.labelImgCaption.frame;
newFrame.size.height = expectedLabelSize.height;
self.labelImgCaption.frame = newFrame;
}
-(void)populateWebView
{
NSString *htmlContentString = [DDUtilities createHTMLStringForArticleDetail:self.parsedNewsArticle];
[self.webView loadHTMLString:htmlContentString baseURL:nil];
}
-(void)checkSavedTextFontSize
{
self.textFontSize = [[DDUserDefaults getValueForKey:#"textFontSize"]integerValue];
if (self.textFontSize != 0) {
NSString *jsString = [[NSString alloc] initWithFormat:#"document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust= '%d%%'",
self.textFontSize];
[self.webView stringByEvaluatingJavaScriptFromString:jsString];
} else {
self.textFontSize = 100;
}
}
#pragma mark -
#pragma mark UIWebView Delegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if (navigationType == UIWebViewNavigationTypeLinkClicked)
{
[[UIApplication sharedApplication] openURL:[request URL]];
return NO;
}
return YES;
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[self checkSavedTextFontSize];
CGRect frame = webView.frame;
frame.size.height = 1;
webView.frame = frame;
CGSize fittingSize = [webView sizeThatFits:CGSizeZero];
frame.size = fittingSize;
dispatch_async(dispatch_get_main_queue(), ^{
self.webView.frame = CGRectMake(frame.origin.x, self.labelImgCaption.frame.origin.y + self.labelImgCaption.frame.size.height + 5.0f, frame.size.width, frame.size.height);
self.contentView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.webView.frame.origin.y + self.webView.frame.size.height + 30.0f);
((UIScrollView*)self.view).contentSize = self.contentView.frame.size;
});
//[DDUtilities setImageView:self.image forLink:self.parsedNewsArticle.articleImgUrl placeholder:YES withActivityIndicator:self.activity];
}
-(void)webViewDidStartLoad:(UIWebView *)webView
{
}
-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
}
#pragma mark -
#pragma mark MBProgressHUDDelegate methods
- (void)hudWasHidden
{
[self.mbProcess removeFromSuperview];
}
#pragma mark -
#pragma mark ParserDelegate methods
-(void)didFinishWithObject:(id)object
{
self.parsedNewsArticle = object;
[self loadData];
}
You create a controller (which has a view). You assign the view as a subview of some other view. That's it. So, ARC will helpfully destroy your article detail controller that you aren't using any more. Anything that it has set itself as the delegate of (like a web view) will now crash when it tries to call the delegate.
Solution: store the article detail controller (add a strong property) so that it is retained while the view is on display.
Or, add the controller as a chile view controller.
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"