I need to zoom and rotate a custom UITextView that I'm already dragging around my UIView. My UITextView has a black border around it.
The problem is that if I zoom the UITextView with a gesture and then rotate my UITextView, the text goes out of bounds and acts weird.
Any suggestion?
~~ ZoomRotatePanTextView.h ~~
#import <UIKit/UIKit.h>
#interface ZoomRotatePanTextView : UITextView
- (void) reset:(BOOL)animation;
#end
~~ ZoomRotatePanTextView.m ~~
#import "ZoomRotatePanTextView.h"
#interface ZoomRotatePanTextView () <UIGestureRecognizerDelegate>
#property (nonatomic, retain) UIPinchGestureRecognizer *pinchRecogniser;
#property (nonatomic, retain) UIRotationGestureRecognizer *rotateRecogniser;
#property (nonatomic, retain) UIPanGestureRecognizer *panRecogniser;
#property (nonatomic, retain) UITapGestureRecognizer *tapRecogniser;
#property (nonatomic, retain) UITapGestureRecognizer *doubleTapRecogniser;
#property (nonatomic, retain) UILongPressGestureRecognizer *longPressRecogniser;
- (void) setUpGestures;
- (IBAction) handlePinch:(UIPinchGestureRecognizer*)recogniser;
- (IBAction) handleRotate:(UIRotationGestureRecognizer*)recogniser;
- (IBAction) handlePan:(UIPanGestureRecognizer*)recogniser;
- (IBAction) handleTap:(UITapGestureRecognizer*)recogniser;
- (IBAction) handleDoubleTap:(UITapGestureRecognizer*)recogniser;
- (IBAction) handleLongPress:(UITapGestureRecognizer*)recogniser;
#end
#implementation ZoomRotatePanTextView
#pragma mark - Initialisation Overrides
- (id) init {
self = [super init];
if (self) {
[self setUpGestures];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setUpGestures];
}
return self;
}
- (id) initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self setUpGestures];
}
return self;
}
- (void) setUpGestures {
[self setUserInteractionEnabled:TRUE];
_pinchRecogniser = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(handlePinch:)];
_rotateRecogniser = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(handleRotate:)];
_panRecogniser = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
_tapRecogniser = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
_doubleTapRecogniser = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleDoubleTap:)];
_longPressRecogniser = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPress:)];
[_pinchRecogniser setDelegate:self];
[_rotateRecogniser setDelegate:self];
[_panRecogniser setDelegate:self];
[_tapRecogniser setDelegate:self];
[_doubleTapRecogniser setDelegate:self];
[_longPressRecogniser setDelegate:self];
[self addGestureRecognizer:_pinchRecogniser];
[self addGestureRecognizer:_rotateRecogniser];
[self addGestureRecognizer:_panRecogniser];
[self addGestureRecognizer:_tapRecogniser];
[self addGestureRecognizer:_doubleTapRecogniser];
[self addGestureRecognizer:_longPressRecogniser];
_tapRecogniser.numberOfTapsRequired = 1;
_doubleTapRecogniser.numberOfTapsRequired = 2;
[_tapRecogniser requireGestureRecognizerToFail:_doubleTapRecogniser];
[self setContentMode:UIViewContentModeScaleAspectFit];
}
- (IBAction) handlePinch:(UIPinchGestureRecognizer*)recogniser {
recogniser.view.transform = CGAffineTransformScale(recogniser.view.transform, recogniser.scale, recogniser.scale);
recogniser.scale = 1;
}
- (IBAction) handleRotate:(UIRotationGestureRecognizer*)recogniser {
recogniser.view.transform = CGAffineTransformRotate(recogniser.view.transform, recogniser.rotation);
recogniser.rotation = 0;
}
- (IBAction) handleTap:(UITapGestureRecognizer*)recogniser {
}
- (IBAction) handleDoubleTap:(UITapGestureRecognizer*)recogniser {
[self reset:YES];
}
- (IBAction) handleLongPress:(UITapGestureRecognizer*)recogniser {
}
- (IBAction) handlePan:(UIPanGestureRecognizer*)recogniser {
if (recogniser.state == UIGestureRecognizerStateBegan || recogniser.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [recogniser translationInView:self.superview];
CGPoint translatedCenter = CGPointMake(self.center.x + translation.x, self.center.y + translation.y);
[self setCenter:translatedCenter];
[recogniser setTranslation:CGPointZero inView:self];
}
}
- (void) reset:(BOOL)animation {
if (!animation) {
self.transform = CGAffineTransformIdentity;
self.center = self.superview.center;
} else {
[UIView animateWithDuration:.25 animations:^{
self.transform = CGAffineTransformIdentity;
self.center = self.superview.center;
}];
}
}
- (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
#end
Related
I'm trying to replicate the implementation In iOS, how do I create a button that is always on top of all other view controllers? in Objective-C.
But the loadView is never called, can someone tell me why?
And how to fix it?
ApplicationFloatingButtonWindow.m
#import "ApplicationFloatingButtonWindow.h"
#import "ApplicationFloatingButtonController.h"
#interface ApplicationFloatingButtonWindow()
#pragma mark - Properties
#property (strong, nonatomic) UIButton *floatingButton;
#property (strong, nonatomic) ApplicationFloatingButtonController *floatingButtonViewController;
#end
#implementation ApplicationFloatingButtonWindow
#pragma mark - Initialization
- (instancetype)init {
self = [super init];
if (self) {
self.backgroundColor = nil; // make background transparent
}
return self;
}
#pragma mark - Gesture Handling
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
if(self.floatingButton) {
CGPoint floatingButtonPoint = [self convertPoint:point toView:self.floatingButton];
return [self.floatingButton pointInside:floatingButtonPoint withEvent:event];
}
return [super pointInside:point withEvent:event];
}
#end
ApplicationFloatingButtonController.h
#import <UIKit/UIKit.h>
#import "ApplicationFloatingButtonWindow.h"
#interface ApplicationFloatingButtonController : UIViewController
#pragma mark - Properties
#property (strong, nonatomic) UIButton *floatingButton;
#property (strong, nonatomic) ApplicationFloatingButtonWindow *window;
#end
ApplicationFloatingButtonController.m
#import "ApplicationFloatingButtonController.h"
#interface ApplicationFloatingButtonController ()
#end
#implementation ApplicationFloatingButtonController
#pragma mark - Initialization
- (instancetype)init {
self = [super initWithNibName:nil bundle:nil];
if (self) {
[self setup];
}
return self;
}
#pragma mark - Setup
- (void)setup {
self.window.windowLevel = CGFLOAT_MAX;
self.window.hidden = NO;
self.window.rootViewController = self;
[NSNotificationCenter.defaultCenter addObserver:self selector:#selector(keyboardDidShow) name:UIKeyboardDidShowNotification object:nil];
}
- (void)setupPanGestureRecognizers {
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panGestureRecognizerAction:)];
[self.floatingButton addGestureRecognizer:panGestureRecognizer];
}
#pragma mark - Lifecycle
- (void)loadView {
UIButton *button = [self createButton];
UIView *view = [UIView new];
[view addSubview:button];
self.view = view;
self.floatingButton = button;
[self setupPanGestureRecognizers];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self snapButtonToBestPosition];
}
#pragma mark - Elements Factory
- (UIButton *)createButton {
UIButton *button = [UIButton buttonWithType:UIButtonTypeInfoDark];
[button setTitle:#"Floating..." forState:UIControlStateNormal];
[button setTitleColor:UIColor.blackColor forState:UIControlStateNormal];
button.backgroundColor = UIColor.whiteColor;
button.layer.shadowColor = UIColor.blackColor.CGColor;
button.layer.shadowRadius = 3;
button.layer.shadowOpacity = 0.8;
button.layer.shadowOffset = CGSizeZero;
[button sizeToFit];
button.frame = CGRectMake(10, 10, 24, 24);
button.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);;
return button;
}
#pragma mark - Selectors
- (void)keyboardDidShow {
// This refreshes the window level and puts it back to the top most view
self.window.windowLevel = 0;
self.window.windowLevel = CGFLOAT_MAX;
}
- (void)panGestureRecognizerAction:(UIPanGestureRecognizer *)panGestureRecognizer {
CGPoint gestureOffset = [panGestureRecognizer translationInView:self.view];
[panGestureRecognizer setTranslation:CGPointZero inView:self.view];
CGPoint newButtonCenter = self.floatingButton.center;
newButtonCenter.x += gestureOffset.x;
newButtonCenter.y += gestureOffset.y;
self.floatingButton.center = newButtonCenter;
if (panGestureRecognizer.state == UIGestureRecognizerStateEnded || panGestureRecognizer.state == UIGestureRecognizerStateCancelled ) {
[UIView animateWithDuration:0.3 animations:^{
[self snapButtonToBestPosition];
}];
}
}
#pragma mark - Elements Layout and Positioning Methods
- (NSArray *)possibleButtonPositions {
CGSize buttonSize = self.floatingButton.bounds.size;
CGRect rect = CGRectInset(self.view.frame, 4 + buttonSize.width / 2, 4 + buttonSize.height / 2);
NSMutableArray *possiblePositions = [NSMutableArray new];
[possiblePositions addObject:[NSValue valueWithCGPoint:CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect))]];
[possiblePositions addObject:[NSValue valueWithCGPoint:CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect))]];
[possiblePositions addObject:[NSValue valueWithCGPoint:CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect))]];
[possiblePositions addObject:[NSValue valueWithCGPoint:CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect))]];
[possiblePositions addObject:[NSValue valueWithCGPoint:CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect))]];
return possiblePositions;
}
- (void)snapButtonToBestPosition {
CGPoint bestPositionForButton = CGPointZero;
CGFloat distanceToBestPosition = CGFLOAT_MAX;
CGPoint buttonCenter = self.floatingButton.center;
for (NSValue *possibleButtonPositionAsNSValue in [self possibleButtonPositions]) {
CGPoint possibleButtonPosition = possibleButtonPositionAsNSValue.CGPointValue;
CGFloat distance = hypot(buttonCenter.x - possibleButtonPosition.x, buttonCenter.y - possibleButtonPosition.y);
if (distance < distanceToBestPosition) {
distanceToBestPosition = distance;
bestPositionForButton = possibleButtonPosition;
}
}
self.floatingButton.center = bestPositionForButton;
}
#end
AppDelegate.m
#import "AppDelegate.h"
#import "ApplicationFloatingButtonController.h"
#interface AppDelegate ()
#pragma mark - Properties
#property (strong, nonatomic) ApplicationFloatingButtonController *floatingButtonController;
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// Configure Floating Button
self.floatingButtonController = [ApplicationFloatingButtonController new];
[self.floatingButtonController.floatingButton addTarget:self action:#selector(floatingButtonDidReceiveTouchUpInside) forControlEvents:UIControlEventTouchUpInside];
return YES;
}
- (void)floatingButtonDidReceiveTouchUpInside {
UIAlertController* alert = [UIAlertController alertControllerWithTitle:#"My Alert"
message:#"This is an alert."
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[alert addAction:defaultAction];
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
}
#end
The solution and final code is here:
https://github.com/bocato/ApplicationFloatingButton
The Swift version has
private let window = FloatingButtonWindow()
in the FloatingButtonController class. You have created the property, in the .h file, but you don't initialise it anywhere. Try adding
self.window = [ApplicationFloatingButtonWindow new];
to your setup method.
I am attempting to create a view to show a list of items and prices using two UILabels in a UIView.
In my UIViewController I call my subview LineItemView and pass data, and return the UIView to the main view that will hold the subviews.
Though my view is returning empty. The strings are null.
ViewController.m
#import "LineItemView.h"
//...
#property (weak, nonatomic) IBOutlet UIView *viewCharges;
//...
- (void)viewDidLoad {
[super viewDidLoad];
[self loadData];
}
- (void) loadData {
//Here we will fill in the viewCharges view
LineItemView * view = [[LineItemView alloc]init];
view.linePrice = #"1.00";
view.lineItem = #"Something";
[self.viewCharges addSubview:view];
}
LineItemView.h
#interface LineItemView : UIView {
UILabel * lblLineItem, *lblLinePrice;
}
#property (nonatomic,strong) NSString* lineItem;
#property (nonatomic,strong) NSString* linePrice;
LineItemView.m
#define LABEL_MINIMUM_HEIGHT 32
#define VERTICAL_MARGIN 5
#define HORIZONTAL_MARGIN 10
#implementation LineItemView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[self createUI];
[self updateData];
NSLog(#"\n\n\n Line Item: %# Price: %# \n\n\n", self.lineItem, self.linePrice);
}
return self;
}
-(void) createUI {
lblLineItem = [[UILabel alloc] initWithFrame:CGRectZero];
lblLineItem.backgroundColor = [UIColor greenColor];
[self addSubview:lblLineItem];
lblLinePrice = [[UILabel alloc] initWithFrame:CGRectZero];
lblLinePrice.backgroundColor = [UIColor yellowColor];
[self addSubview:lblLinePrice];
}
- (void) updateLayout {
lblLineItem.frame = CGRectMake(HORIZONTAL_MARGIN, VERTICAL_MARGIN, 300, 35);
lblLinePrice.frame = CGRectMake(lblLineItem.frame.origin.x + lblLineItem.frame.size.width + HORIZONTAL_MARGIN, VERTICAL_MARGIN, 80, 35);
}
- (void) updateData {
lblLineItem.text = self.lineItem;
lblLinePrice.text = [NSString stringWithFormat:#"%0.2f", [self.linePrice floatValue]];
}
- (void) layoutSubviews {
[super layoutSubviews];
[self updateLayout];
}
What am I doing wrong?
If I want to continue calling LineItemView, how do I ensure that it is added below the previous one and ensure the viewCharges size is readjusted to fit all the subviews?
Solution using setters:
#implementation LineItemView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[self createUI];
}
return self;
}
-(void) createUI {
lblLineItem = [[UILabel alloc] initWithFrame:CGRectZero];
lblLineItem.backgroundColor = [UIColor greenColor];
[self addSubview:lblLineItem];
lblLinePrice = [[UILabel alloc] initWithFrame:CGRectZero];
lblLinePrice.backgroundColor = [UIColor yellowColor];
[self addSubview:lblLinePrice];
}
- (void) updateLayout {
lblLineItem.frame = CGRectMake(HORIZONTAL_MARGIN, VERTICAL_MARGIN, 300, 35);
lblLinePrice.frame = CGRectMake(lblLineItem.frame.origin.x + lblLineItem.frame.size.width + HORIZONTAL_MARGIN, VERTICAL_MARGIN, 80, 35);
}
- (void) layoutSubviews {
[super layoutSubviews];
[self updateLayout];
}
- (void)setLineItem:(NSSTring *)lineItem {
_lineItem = lineItem;
lblLineItem.text = self.lineItem;
}
- (void)setLinePrice:(NSNumber *)linePrice {
_linePrice = linePrice;
lblLinePrice.text = [NSString stringWithFormat:#"%0.2f", [self.linePrice floatValue]];
}
I'm using UIDynamics in my app. my app has two squares. one is fixed and the other can be moved (by applying a pan gesture). when i try to collide them they don't collide. the delegate methods never get called. here's my code and i hope someone can point out the problem.
UIDynamicAnimator* animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
UICollisionBehavior* collisionBehavior = [[UICollisionBehavior alloc] initWithItems:#[self.square1, self.square2]];
collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[self.square1 addGestureRecognizer:pan];
[collisionBehavior setCollisionMode:UICollisionBehaviorModeEverything];
[animator addBehavior:collisionBehavior];
collisionBehavior.collisionDelegate = self;
Make animator as a property or instance variable. otherwise it will
be cleared after view load.
Make sure implemented UICollisionBehaviorDelegate protol and correspond methods
Here is my works implementation based on your code.
Interface:
#import <UIKit/UIKit.h>
#interface DynamicViewController : UIViewController<UICollisionBehaviorDelegate>
#property (strong, nonatomic) UIDynamicAnimator *animator;
#property (strong, nonatomic) UIPushBehavior *pushBehavior;
#end
Implementation:
#synthesize pushBehavior = _pushBehavior;
#synthesize animator = _animator;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *square1 = [[UIView alloc] initWithFrame:CGRectMake(100, 30, 30, 30)];
square1.center = CGPointMake(150, 150);
square1.backgroundColor = [UIColor greenColor];
UIView *square2 = [[UIView alloc] initWithFrame:CGRectMake(100, 300, 30, 30)];
square2.center = CGPointMake(250, 350);
square2.backgroundColor = [UIColor redColor];
[self.view addSubview:square1];
[self.view addSubview:square2];
_animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
UICollisionBehavior* collisionBehavior = [[UICollisionBehavior alloc] initWithItems:#[square1, square2]];
collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
[collisionBehavior setCollisionMode:UICollisionBehaviorModeEverything];
collisionBehavior.collisionDelegate = self;
[_animator addBehavior:collisionBehavior];
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[square1 addGestureRecognizer:pan];
}
#pragma mark push behavior
- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
if(gesture.state == UIGestureRecognizerStateBegan){
if (_pushBehavior) {
[_animator removeBehavior:_pushBehavior];
}
_pushBehavior = [[UIPushBehavior alloc] initWithItems:#[gesture.view] mode:UIPushBehaviorModeContinuous];
[_animator addBehavior:_pushBehavior];
}
CGPoint offset =[gesture translationInView:self.view];
_pushBehavior.pushDirection = CGVectorMake(offset.x,offset.y);
_pushBehavior.magnitude = sqrtf(offset.x * offset.x + offset.y * offset.y) / 50;
if (gesture.state == UIGestureRecognizerStateEnded) {
[_animator removeBehavior:_pushBehavior];
_pushBehavior = nil;
}
}
#pragma mark Colision delegate
- (void)collisionBehavior:(UICollisionBehavior*)behavior beganContactForItem:(id <UIDynamicItem>)item1 withItem:(id <UIDynamicItem>)item2 atPoint:(CGPoint)p;
{
NSLog(#"Colision Began");
}
- (void)collisionBehavior:(UICollisionBehavior*)behavior endedContactForItem:(id <UIDynamicItem>)item1 withItem:(id <UIDynamicItem>)item2;
{
NSLog(#"Colision End");
}
Your UIDynamicAnimator* animator is destroyed when function ends due to ARC. Thats why you are not able to see any animations. Make it a instance variable and you will be fine.
I think there is some bug with UIDynamics (but not 100% sure let me know if I'm wrong).
I couldn't make my dynamic works until I created instance variables for UIDynamicAnimator and UIGravityBehavior.
Try add instance variable to the .m file in class extension as:
UIDynamicAnimator* animator;
UICollisionBehavior* collisionBehavior;
And change your first two line of code to:
animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
collisionBehavior = [[UICollisionBehavior alloc] initWithItems:#[self.square1, self.square2]];
It should help.
You have to specify the bounds using "addBoundaryWithIdentifier" for the collision class and remove the non-moving item form the collision
I've edited this question a few times, but can still not get my images to center inside a uiview. I want them to be able to rotate like the photos app and display the correct size when a user brings them up. Here is what I'm working with:
In my PhotoViewController.h
#import <UIKit/UIKit.h>
#protocol PhotoViewControllerDelegate <NSObject>
- (void)toggleChromeDisplay;
#end
#interface PhotoViewController : UIViewController <UIScrollViewDelegate, UIGestureRecognizerDelegate>
#property (nonatomic, strong) UIImage *photo;
#property (nonatomic) NSUInteger num;
//Delegate
#property (nonatomic, strong) id<PhotoViewControllerDelegate> photoViewControllerDelegate;
#property (nonatomic, strong) UIImageView *photoImgView;
#property (nonatomic, strong) UIScrollView *scrollView;
#end
In my PhotoViewController.m:
#import "PhotoViewController.h"
#interface PhotoViewController ()
#end
#implementation PhotoViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
//todo
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
CGRect screenBounds = self.view.bounds;
//scroll view
_scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, screenBounds.size.width, screenBounds.size.height)];
_scrollView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
_scrollView.pagingEnabled = NO;
_scrollView.scrollEnabled = YES;
[_scrollView setBackgroundColor:[UIColor blueColor]];
//Zoom Properties
_scrollView.maximumZoomScale = 6.0;
_scrollView.minimumZoomScale = 1.0;
_scrollView.bouncesZoom = YES;
_scrollView.delegate = self;
_scrollView.zoomScale = 1.0;
_scrollView.contentSize = _photoImgView.bounds.size;
[_scrollView setShowsHorizontalScrollIndicator:NO];
[_scrollView setShowsVerticalScrollIndicator:NO];
[self photoBounds];
[self.view addSubview: _scrollView];
//Add the UIImageView
_photoImgView = [[UIImageView alloc] initWithImage:_photo];
_photoImgView.image = _photo;
_photoImgView.clipsToBounds = YES;
_photoImgView.contentMode = UIViewContentModeScaleAspectFit;
_photoImgView.autoresizingMask = (UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleLeftMargin);
[_photoImgView setUserInteractionEnabled:YES];
[_scrollView addSubview: _photoImgView];
//Set up Gesture Recognizer
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(singleTapGestureCaptured:)];
UITapGestureRecognizer *dTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(dTapGestureCaptured:)];
dTap.numberOfTapsRequired = 2;
[singleTap requireGestureRecognizerToFail:dTap];
//Gesture Methods
[self.scrollView addGestureRecognizer:singleTap];
[self.scrollView addGestureRecognizer : dTap];
}
- (void)photoBounds
{
UIInterfaceOrientation statusbar = [[UIApplication sharedApplication] statusBarOrientation];
CGSize photoBounds = _photo.size;
CGSize scrollBounds = self.view.bounds.size;
CGRect frameToCenter = [_photoImgView frame];
float newHeight = (scrollBounds.width / photoBounds.width) * photoBounds.height;
float newWidth = (scrollBounds.height / photoBounds.height) * photoBounds.width;
float yDist = fabsf(scrollBounds.height - newHeight) / 2;
float xDist = fabsf(scrollBounds.width - newWidth) / 2;
//Width Larger
if (photoBounds.width >=photoBounds.height) {
NSLog(#"portrait width");
_photoImgView.frame = CGRectMake(0, 0, scrollBounds.width, newHeight);
frameToCenter.origin.y = yDist;
}
//Height Larger
else if (photoBounds.height > photoBounds.width) {
NSLog(#"portrait height");
_photoImgView.frame = CGRectMake(0, 0, newWidth, scrollBounds.height);
frameToCenter.origin.x = xDist;
}
//Square
else {
NSLog(#"portrait square");
if ((statusbar == 1) || (statusbar == 2)) {
_photoImgView.frame = CGRectMake(0, 0, scrollBounds.width, newHeight);
frameToCenter.origin.y = yDist;
} else {
_photoImgView.frame = CGRectMake(0, 0, newWidth, scrollBounds.height);
frameToCenter.origin.x = xDist;
}
}
}
//Rotation Magic
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
//later
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self photoBounds];
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
//
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
//Zoom Ability
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.photoImgView;
}
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale
{
NSLog(#"scale %f", scale);
NSLog(#"done zooming");
}
//Touches Control
- (void)singleTapGestureCaptured:(UITapGestureRecognizer *)gesture
{
//CGPoint touchPoint=[gesture locationInView:_scrollView];
NSLog(#"touched");
NSLog(#"single touch");
[self performSelector:#selector(callingHome) withObject:nil afterDelay:0];
}
- (void)dTapGestureCaptured:(UITapGestureRecognizer *)gesture
{
NSLog(#"double touched");
}
- (void)panGestureCaptured:(UIPanGestureRecognizer *)gesture
{
NSLog(#"pan gesture");
}
- (void)callingHome {}
#end
The overall issue is that I can not get my picture to display correctly and be able to zoom on just it, no space around it and It needs to be the correct dimensions on load. I've been struggling with it for a few days.
Any help?
I have resolved a similar problem using a really simple subclass of UIScrollView of my own. You can take a look at it here - https://gist.github.com/ShadeApps/5a29e1cea3e1dc3df8c8. Works like charm for me.
That's how you init it:
scrollViewMain.delegate = self;
scrollViewMain.minimumZoomScale = 1.0;
scrollViewMain.maximumZoomScale = 3.0;
scrollViewMain.contentSize = imageViewMain.frame.size;
scrollViewMain.backgroundColor = [UIColor blackColor];
scrollViewMain.tileContainerView = imageViewMain;
There is awesome blog post about this problem from Peter Steinberger: http://petersteinberger.com/blog/2013/how-to-center-uiscrollview/
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.