How do you infinitely loop all property modifications within UIView animation blocks - objective-c

I'm having an issue with making a glowing animation for UIView elements infinitely increase the size and opacity of the object shadow that I'm using to create the glow.
I've tried using different animation options, but none result in the shadow properties changing infinitely, only the animation that increases the size of the buttons infinitely loops.
- (void)addGlow:(UIView *)element withColor:(UIColor *)color
{
element.layer.shadowColor = color.CGColor;
element.layer.shadowOpacity = 0;
element.layer.shadowOffset = CGSizeZero;
element.layer.shadowRadius = 0;
[UIView animateWithDuration:0.6 delay:0 options: UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse | UIViewAnimationCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState
animations:^
{
element.transform = CGAffineTransformMakeScale(1.02, 1.02);
element.layer.shadowOpacity = 0.5;
element.layer.shadowRadius = 5;
}
completion:NULL];
}
I basically just want the shadowOpacity and shadowRadius to also increase and decrease infinitely, alongside the UIView object's pulsing effect (due to the transform).

I'd use CoreAnimation's CABasicAnimation + CAAnimationGroup, here's an example or what I think you're attempting:
#interface ViewController ()
#property (strong, nullable) IBOutlet UIButton *buttonToGlow;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.buttonToGlow.backgroundColor = [UIColor lightGrayColor];
self.buttonToGlow.layer.cornerRadius = 10.0f;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self addGlow:self.buttonToGlow withColor:[UIColor redColor]];
}
- (void)addGlow:(UIView *)element withColor:(UIColor *)color {
[element.layer removeAnimationForKey:#"glowAnimation"];
element.layer.shadowColor = color.CGColor;
element.layer.shadowOffset = CGSizeZero;
CABasicAnimation *shadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:#"shadowOpacity"];
shadowOpacityAnimation.fromValue = #(0);
shadowOpacityAnimation.toValue = #(0.5);
CABasicAnimation *shadowRadiusAnimation = [CABasicAnimation animationWithKeyPath:#"shadowRadius"];
shadowRadiusAnimation.fromValue = #(0);
shadowRadiusAnimation.toValue = #(5);
CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
scaleAnimation.fromValue = #(1.0);
scaleAnimation.toValue = #(1.02);
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.autoreverses = YES;
animationGroup.repeatCount = CGFLOAT_MAX;
animationGroup.duration = 0.6f;
animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animationGroup.animations = #[shadowOpacityAnimation, shadowRadiusAnimation, scaleAnimation];
[element.layer addAnimation:animationGroup forKey:#"glowAnimation"];
}
#end
Here's the result:
References:
CoreAnimation programming guide: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html?language=objc#//apple_ref/doc/uid/TP40004514
CABasicAnimation: https://developer.apple.com/documentation/quartzcore/cabasicanimation?language=objc
CAAnimationGroup: https://developer.apple.com/documentation/quartzcore/caanimationgroup?language=objc

Related

xCode Paper Onboarding effect

How can we implement Paper Onboarding effect. I have one containerview and 3viewconroller inside that. can we implemented same below thing?
this is snapchat chat camera moving effect
I think you have to read this issue.
https://www.objc.io/issues/12-animations/custom-container-view-controller-transitions/
Understand the first two stages first.
Then download Stage 3: Shrink-Wrapping code from github. Under that code replace "PrivateAnimatedTransition" implementation at last with following code.
#implementation PrivateAnimatedTransition
static CGFloat const kChildViewPadding = 16;
static CGFloat const kDamping = 0.75;
static CGFloat const kInitialSpringVelocity = 0.5;
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
return 0.3;
}
/// Slide views horizontally, with a bit of space between, while fading out and in.
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
UIView *containerView = [transitionContext containerView];
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//UIButton *button = fromViewController.button;
[containerView addSubview:toViewController.view];
CGRect buttonFrame = CGRectMake(300, 450, 10, 10);
UIBezierPath *circleMaskPathInitial = [UIBezierPath bezierPathWithOvalInRect:buttonFrame];
CGPoint extremePoint = CGPointMake(305, 455);
CGFloat radius = sqrt((extremePoint.x * extremePoint.x) + (extremePoint.y * extremePoint.y));
UIBezierPath *circleMaskPathFinal = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(buttonFrame, -radius, -radius)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.path = circleMaskPathFinal.CGPath;
toViewController.view.layer.mask = maskLayer;
CABasicAnimation *maskLayerAnimation = [CABasicAnimation animationWithKeyPath:#"path"];
maskLayerAnimation.fromValue = (__bridge id _Nullable)(circleMaskPathInitial.CGPath);
maskLayerAnimation.toValue = (__bridge id _Nullable)(circleMaskPathFinal.CGPath);
maskLayerAnimation.duration = [self transitionDuration:transitionContext];
[maskLayer addAnimation:maskLayerAnimation forKey:#"path"];
[self performSelector:#selector(finishTransition:) withObject:transitionContext afterDelay:[self transitionDuration:transitionContext]];
}
- (void)finishTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey].view.layer.mask = nil;
}
and "BINGO" you have done. Just replace buttonFrame with actual selected index button's frame. Feel free to ask any query

Variables not updating while using CADisplayLink

I cannot get the variables to change when I update them. The method "reCenterYaw" fires when I touch the button and the variable "self.hasYawCenterPointBeenSet" gets updated within the method but when "updateCycle" method comes back around the variable is not showing it was updated. updateCycle is my CADisplaylink driven method. I've tried updating the variables to atomic, referenced them as their instance variables with "_" and with "self." Not sure what is going on.
#import "GameViewController.h"
#import <CoreMotion/CoreMotion.h>
#import <SpriteKit/SpriteKit.h>
#import "HUDOverlay.h"
#interface GameViewController ()
#property (strong, nonatomic) CMMotionManager *motionManager;
#property (strong, nonatomic) CMAttitude *currentAttitude;
#property (strong, nonatomic) SCNNode *cameraNode;
#property (strong, nonatomic) SCNScene *scene;
#property (nonatomic) float roll;
#property (nonatomic) float yaw;
#property (nonatomic) float pitch;
#property (atomic) float yawCenterPoint;
#property (atomic) BOOL hasYawCenterPointBeenSet;
#end
GameViewController* sharedGameViewController = nil;
#implementation GameViewController
+(GameViewController*)sharedController{
if (sharedGameViewController == nil) {
sharedGameViewController = [[GameViewController alloc] init];
}
return sharedGameViewController;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// create a new scene
//SCNScene *scene = [SCNScene sceneNamed:#"art.scnassets/ship.dae"];
_scene = [SCNScene new];
self.hasYawCenterPointBeenSet = NO;
// create and add a camera to the scene
_cameraNode = [SCNNode node];
_cameraNode.camera = [SCNCamera camera];
[_scene.rootNode addChildNode:_cameraNode];
// place the camera
_cameraNode.position = SCNVector3Make(0, 0, 0);
// create and add a light to the scene
SCNNode *lightNode = [SCNNode node];
lightNode.light = [SCNLight light];
lightNode.light.type = SCNLightTypeOmni;
lightNode.position = SCNVector3Make(0, 10, 10);
[_scene.rootNode addChildNode:lightNode];
// create and add an ambient light to the scene
SCNNode *ambientLightNode = [SCNNode node];
ambientLightNode.light = [SCNLight light];
ambientLightNode.light.type = SCNLightTypeAmbient;
ambientLightNode.light.color = [UIColor whiteColor];
[_scene.rootNode addChildNode:ambientLightNode];
// retrieve the SCNView
SCNView *scnView = (SCNView *)self.view;
scnView.delegate = self;
scnView.playing = YES;
// set the scene to the view
scnView.scene = _scene;
scnView.overlaySKScene = [HUDOverlay sceneWithSize:scnView.frame.size];
// allows the user to manipulate the camera
scnView.allowsCameraControl = NO;
// show statistics such as fps and timing information
scnView.showsStatistics = YES;
// configure the view
scnView.backgroundColor = [UIColor blueColor];
// // add a tap gesture recognizer
// UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
// NSMutableArray *gestureRecognizers = [NSMutableArray array];
// [gestureRecognizers addObject:tapGesture];
// [gestureRecognizers addObjectsFromArray:scnView.gestureRecognizers];
// scnView.gestureRecognizers = gestureRecognizers;
SCNNode *sampleNode = [SCNNode new];
SCNPlane *samplePlane = [SCNPlane planeWithWidth:3 height:5];
SCNMaterial *angryFrontMaterial = [SCNMaterial material];
angryFrontMaterial.diffuse.contents = [UIImage imageNamed:#"angryfront110.png"];
sampleNode.geometry = samplePlane;
sampleNode.geometry.firstMaterial = angryFrontMaterial;
sampleNode.position = SCNVector3Make(0, 0, -10);
[_scene.rootNode addChildNode:sampleNode];
// SCNAction *moveToCamera = [SCNAction moveTo:SCNVector3Make(0, 0, -10) duration:10];
// [sampleNode runAction:moveToCamera];
[self setupRotationGrid];
[self setupMotionCapture];
displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(updateCycle)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
//This is the update loop for the sceneView. When the view updates per frame this is where the logic goes
-(void)renderer:(id<SCNSceneRenderer>)aRenderer updateAtTime:(NSTimeInterval)time{
}
-(void)updateCycle{
_currentAttitude = _motionManager.deviceMotion.attitude;
_roll = _currentAttitude.roll + (.5 * M_PI);
_yaw = _currentAttitude.yaw;
_pitch = _currentAttitude.pitch;
_cameraNode.eulerAngles = SCNVector3Make(-_roll, _yaw, _pitch);
//NSLog(#"yawCenterPoint = %d", _hasYawCenterPointBeenSet);
if (self.hasYawCenterPointBeenSet == NO) {
_yawCenterPoint = _yaw;
self.hasYawCenterPointBeenSet = YES;
NSLog(#"Yawcenter point set to %f", _yawCenterPoint);
}
//NSLog(#"Roll: %f Yaw: %f Pitch: %f", _roll, _yaw, _pitch);
}
-(void)setupRotationGrid {
double radius = 30;
double numberOfItems = 8;
SCNGeometry *geometry = [SCNBox boxWithWidth:4.5 height:8 length:3.25 chamferRadius:0.5];
double x = 0.0;
double z = radius;
double theta = (M_PI)/(numberOfItems/2);
double incrementalY = (M_PI)/(numberOfItems*2);
SCNNode *nodeCollection = [SCNNode node];
nodeCollection.position = SCNVector3Make(0, 4, 0);
for (int index = 1; index <= numberOfItems; index++) {
x = radius * sin(index*theta);
z = radius * cos(index*theta);
SCNNode *node = [SCNNode node];
node.geometry = geometry;
node.position = SCNVector3Make(x, 0, z);
float rotation = incrementalY * index;
node.rotation = SCNVector4Make(0, 1, 0, rotation);
[nodeCollection addChildNode:node];
}
[_scene.rootNode addChildNode:nodeCollection];
}
- (void) handleTap:(UIGestureRecognizer*)gestureRecognize
{
// retrieve the SCNView
SCNView *scnView = (SCNView *)self.view;
// check what nodes are tapped
CGPoint p = [gestureRecognize locationInView:scnView];
NSArray *hitResults = [scnView hitTest:p options:nil];
// check that we clicked on at least one object
if([hitResults count] > 0){
// retrieved the first clicked object
SCNHitTestResult *result = [hitResults objectAtIndex:0];
// get its material
SCNMaterial *material = result.node.geometry.firstMaterial;
// highlight it
[SCNTransaction begin];
[SCNTransaction setAnimationDuration:0.5];
// on completion - unhighlight
[SCNTransaction setCompletionBlock:^{
[SCNTransaction begin];
[SCNTransaction setAnimationDuration:0.5];
material.emission.contents = [UIColor blackColor];
[SCNTransaction commit];
}];
material.emission.contents = [UIColor redColor];
[SCNTransaction commit];
}
}
-(void)reCenterYaw{
self.hasYawCenterPointBeenSet = NO;
NSLog(#"Tapped recenter");
}
-(void)setupMotionCapture{
_motionManager = [[CMMotionManager alloc] init];
_motionManager.deviceMotionUpdateInterval = 1.0/60.0;
if (_motionManager.isDeviceMotionAvailable) {
[_motionManager startDeviceMotionUpdates];
_currentAttitude = _motionManager.deviceMotion.attitude;
}
// if (!_hasYawCenterPointBeenSet) {
// _yawCenterPoint = _currentAttitude.yaw;
// _hasYawCenterPointBeenSet = YES;
// }
}
- (BOOL)shouldAutorotate
{
return YES;
}
- (BOOL)prefersStatusBarHidden {
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
return UIInterfaceOrientationMaskLandscape;
} else {
return UIInterfaceOrientationMaskAll;
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#end
I figured it out. I had created a singleton so that I could reference the view controller from the sprite kit hud class. The problem is that I load the view controller from the storyboard and then later, from the hud class init, I instantiate a new view controller because the "sharedViewController" never got initiated by the storyboard load. So, I just created a view controller property on the hud class and set it to self from the view controller did load code.

iOS animations using CADisplayLink

I'm using animation and timing with CADisplayLink. When the ball touches a big stone it is removed immediately. How can I animate the removal of the stone (block) like exploding to small stones? Here's the code I currently have:
- (void)checkCollisionWithBlocks
{
for (BlockView *bv in blocks) {
if (CGRectIntersectsRect(bv.frame, ballRect)) {
ballVelocity.y = -ballVelocity.y;
[blocks removeObject:bv];
[bv removeFromSuperview];
break;
}
}
}
CADisplayLink is really a bit... much for what you're planning (it's for synchronizing screen drawing updates to a custom animation). Here's my solution: set some things on fire! While it's not necessarily an explosion, this will cause your block to belch some blue fire (you can change the color by modifying self.fireColor).
- (void)checkCollisionWithBlocks
{
for (BlockView *bv in blocks) {
if (CGRectIntersectsRect(bv.frame, ballRect)) {
ballVelocity.y = -ballVelocity.y;
[blocks removeObject:bv];
[bv burpFire]
[bv removeFromSuperview];
break;
}
}
}
#interface BlockView : UIView
#property (nonatomic, strong) CALayer *rootLayer;
#property (nonatomic, strong) CAEmitterLayer *fireEmitter;
#property (nonatomic, strong) CAEmitterLayer *smokeEmitter;
#property (nonatomic, strong) UIColor *fireColor;
#property (nonatomic, strong) UIColor *smokeColor;
-(void)burpFire;
#end
#implementation BlockView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.rootLayer = [CALayer layer];
//Set the root layer's background color to black
self.rootLayer.backgroundColor = [UIColor blackColor].CGColor;
//Create the fire emitter layer
self.fireEmitter = [CAEmitterLayer layer];
self.fireEmitter.emitterPosition = CGPointMake(0, CGRectGetMaxY(frame) + 50);
self.fireEmitter.emitterMode = kCAEmitterLayerOutline;
self.fireEmitter.emitterShape = kCAEmitterLayerLine;
self.fireEmitter.renderMode = kCAEmitterLayerAdditive;
self.fireEmitter.emitterSize = CGSizeMake(0, 0);
//Create the smoke emitter layer
self.smokeEmitter = [CAEmitterLayer layer];
self.smokeEmitter.emitterPosition = CGPointMake(0, CGRectGetMaxY(frame) + 50);
self.smokeEmitter.emitterMode = kCAEmitterLayerPoints;
//Create the fire emitter cell
CAEmitterCell* fire = [CAEmitterCell emitterCell];
fire.emissionLongitude = M_PI;
fire.birthRate = 0;
fire.velocity = -80;
fire.velocityRange = -30;
fire.emissionRange = -1.1;
fire.yAcceleration = -200;
fire.scaleSpeed = 0.3;
self.fireColor = [UIColor colorWithRed:0.425 green:0.779 blue:0.800 alpha:0.100];
fire.color = self.fireColor.CGColor;
fire.contents = (id)[self CGImageNamed:#"fire.png"];
//Name the cell so that it can be animated later using keypaths
[fire setName:#"fire"];
//Add the fire emitter cell to the fire emitter layer
self.fireEmitter.emitterCells = [NSArray arrayWithObject:fire] ;
//Create the smoke emitter cell
CAEmitterCell* smoke = [CAEmitterCell emitterCell];
smoke.birthRate = 11;
smoke.emissionLongitude = M_PI / 2;
smoke.lifetime = 0;
smoke.velocity = -40;
smoke.velocityRange = 20;
smoke.emissionRange = M_PI / 4;
smoke.spin = 1;
smoke.spinRange = 6;
smoke.yAcceleration = 160;
smoke.contents = (id) [self CGImageNamed:#"smoke.png"];
smoke.scale = 0.1;
smoke.alphaSpeed = -0.12;
smoke.scaleSpeed = 0.7;
//Name the cell so that it can be animated later using keypaths
[smoke setName:#"smoke"];
//Add the smoke emitter cell to the smoke emitter layer
self.smokeEmitter.emitterCells = [NSArray arrayWithObject:smoke];
//Add the two emitter layers to the root layer
[self.layer addSublayer:self.smokeEmitter];
[self.layer addSublayer:self.fireEmitter];
[self burpFire];
}
return self;
}
//Return a CGImageRef from the specified image file in the app's bundle
-(CGImageRef)CGImageNamed:(NSString*)name {
NSString *url = [[NSBundle mainBundle] pathForResource:name ofType:nil];
return [UIImage imageWithContentsOfFile:url].CGImage;
}
-(void)burpFire {
float gas = 80.0 / 100.0;
//Update the fire properties
[self.fireEmitter setValue:[NSNumber numberWithInt:(gas * 1000)] forKeyPath:#"emitterCells.fire.birthRate"];
[self.fireEmitter setValue:[NSNumber numberWithFloat:gas] forKeyPath:#"emitterCells.fire.lifetime"];
[self.fireEmitter setValue:[NSNumber numberWithFloat:(gas * 0.35)] forKeyPath:#"emitterCells.fire.lifetimeRange"];
self.fireEmitter.emitterSize = CGSizeMake(100 * gas, 0);
//Update the smoke properites
[self.smokeEmitter setValue:[NSNumber numberWithInt:gas * 4] forKeyPath:#"emitterCells.smoke.lifetime"];
self.smokeColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:gas * 0.3];
[self.smokeEmitter setValue:(__bridge id)self.smokeColor.CGColor forKeyPath:#"emitterCells.smoke.color"];
[self setNeedsDisplay];
[self performSelector:#selector(burpFinished) withObject:nil afterDelay:1.5];
}
-(void)burpFinished {
float gas = 0.0 / 100.0;
//Update the fire properties
[self.fireEmitter setValue:[NSNumber numberWithInt:(gas * 1000)] forKeyPath:#"emitterCells.fire.birthRate"];
[self.fireEmitter setValue:[NSNumber numberWithFloat:gas] forKeyPath:#"emitterCells.fire.lifetime"];
[self.fireEmitter setValue:[NSNumber numberWithFloat:(gas * 0.35)] forKeyPath:#"emitterCells.fire.lifetimeRange"];
self.fireEmitter.emitterSize = CGSizeMake(100 * gas, 0);
//Update the smoke properites
[self.smokeEmitter setValue:[NSNumber numberWithInt:gas * 4] forKeyPath:#"emitterCells.smoke.lifetime"];
self.smokeColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:gas * 0.3];
[self.smokeEmitter setValue:(__bridge id)self.smokeColor.CGColor forKeyPath:#"emitterCells.smoke.color"];
}
The CAEmitterLayers last for quite a while, so if you want the fire to shut off a little faster, play around with the timing of the stop selector. If you want smaller or larger fire, or fireballs that stick around longer, play around with the properties in the init method, either way you're gonna get a cool effect.
All colors must be strong references to UIColor, else Core Animation will freak out and throw a C++ temper tantrum in your debugger... not pleasant.

Multiple images/buttons staying relative to their position on a zoomable image

I've made a map with UIScrolView, I want to place other small images or buttons onto the map and have them be in a relative position on the map when you zoom in and be able to click on them whenever. So when zoomed out, a button on country A, will still be on Country A when zoomed in, and disappear out of the screen when you scroll out of the countries view whilst zoomed in. How could I go about doing this?
As i can understand, you want to place custom views on your own custom map. And you need to keep the same sizes for views, but they should move when you scroll or zoom imageView.
You have to place views to scrollView's superview and recalculate positions when you zoom or scroll:
CustomMapViewController.h:
#interface CustomMapViewController : UIViewController <UIScrollViewDelegate>
{
UIScrollView *_scrollView;
UIImageView *_mapImageView;
NSArray *_customViews;
}
CustomMapViewController.m:
#import "CustomMapViewController.h"
enum {
kAddContactButton = 1,
kInfoDarkButton,
kInfoLightButton,
kLogoImage,
};
#implementation CustomMapViewController
- (void)dealloc
{
[_scrollView release]; _scrollView = nil;
[_mapImageView release]; _mapImageView = nil;
[_customViews release]; _customViews = nil;
[super dealloc];
}
- (void) loadView
{
[super loadView];
UIImageView *mapImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"map.png"]];
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
scrollView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
scrollView.delegate = self;
scrollView.minimumZoomScale = 0.2;
scrollView.maximumZoomScale = 2.0;
[scrollView addSubview:mapImageView];
scrollView.contentSize = mapImageView.frame.size;
[self.view addSubview:scrollView];
_scrollView = scrollView;
_mapImageView = mapImageView;
// Add custom views
UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeContactAdd];
btn1.tag = kAddContactButton;
[self.view addSubview:btn1];
UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeInfoDark];
btn2.tag = kInfoDarkButton;
[self.view addSubview:btn2];
UIButton *btn3 = [UIButton buttonWithType:UIButtonTypeInfoLight];
btn3.tag = kInfoLightButton;
[self.view addSubview:btn3];
UIImageView *image = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"logo.png"]] autorelease];
image.tag = kLogoImage;
[self.view addSubview:image];
_customViews = [[NSArray alloc] initWithObjects:btn1, btn2, btn3, image, nil];
[self _zoomToFit];
}
- (void) _zoomToFit
{
UIScrollView *scrollView = _scrollView;
CGFloat contentWidth = scrollView.contentSize.width;
CGFloat contentHeigth = scrollView.contentSize.height;
CGFloat viewWidth = scrollView.frame.size.width;
CGFloat viewHeight = scrollView.frame.size.height;
CGFloat width = viewWidth / contentWidth;
CGFloat heigth = viewHeight / contentHeigth;
CGFloat scale = MIN(width, heigth); // to fit
// CGFloat scale = MAX(width, heigth); // to fill
// May be should add something like this
if ( scale < _scrollView.minimumZoomScale ) {
_scrollView.minimumZoomScale = scale;
} else if ( scale > _scrollView.maximumZoomScale ) {
_scrollView.maximumZoomScale = scale;
}
_scrollView.zoomScale = scale;
}
////////////////////////////////////////////////////////////////////////////////////////
#pragma mark - Positions
- (void) _updatePositionForViews:(NSArray *)views
{
CGFloat scale = _scrollView.zoomScale;
CGPoint contentOffset = _scrollView.contentOffset;
for ( UIView *view in views ) {
CGPoint basePosition = [self _basePositionForView:view];
[self _updatePositionForView:view scale:scale basePosition:basePosition offset:contentOffset];
}
}
- (CGPoint) _basePositionForView:(UIView *)view
{
switch (view.tag) {
case kAddContactButton:
return CGPointMake(50.0, 50.0);
case kInfoDarkButton:
return CGPointMake(250.0, 250.0);
case kInfoLightButton:
return CGPointMake(450.0, 250.0);
case kLogoImage:
return CGPointMake(650.0, 450.0);
default:
return CGPointZero;
}
}
- (void) _updatePositionForView:(UIView *)view scale:(CGFloat)scale basePosition:(CGPoint)basePosition offset:(CGPoint)offset;
{
CGPoint position;
position.x = (basePosition.x * scale) - offset.x;
position.y = (basePosition.y * scale) - offset.y;
CGRect frame = view.frame;
frame.origin = position;
view.frame = frame;
}
//////////////////////////////////////////////////////////////////////////////////////
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
[self _updatePositionForViews:_customViews];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[self _updatePositionForViews:_customViews];
}
- (UIView *) viewForZoomingInScrollView:(UIScrollView *)scrollView;
{
return _mapImageView;
}
#end

UIPageControl doesn't properly react to taps

In my TestApp, I created a class "Carousel" which should let me create a swipe menu with a UIPageControl in an easy way (by simply creating an instance of the class Carousel).
Carousel is a subclass of UIView
At init, it creates an UIView, containing UIScrollView, UIPageControl
I can add further UIViews to the scroll view
I don't know if this is the proper way to do it, but my example worked quite well in my TestApp. Swiping between pages works perfectly and the display of the current page in the UIPageControl is correct.
If there were not one single problem: The UIPageControl sometimes reacts to clicks/taps (I only tested in Simulator so far!), sometimes it doesn't. Let's say most of the time it doesn't. I couldn't find out yet when it does, for me it's just random...
As you can see below, I added
[pageControl addTarget:self action:#selector(pageChange:) forControlEvents:UIControlEventValueChanged];
to my code. I thought this would do the proper handling of taps? But unfortunately, pageChange doesn't get always called (so the value of the UIPageControl doesn't change every time I click).
I would appreciate any input on this because I couldn't find any solution on this yet.
This is what I have so far:
Carousel.h
#import <UIKit/UIKit.h>
#interface Carousel : UIView {
UIScrollView *scrollView;
UIPageControl *pageControl;
BOOL pageControlBeingUsed;
}
- (void)addView:(UIView *)view;
- (void)setTotalPages:(NSUInteger)pages;
- (void)setCurrentPage:(NSUInteger)current;
- (void)createPageControlAt:(CGRect)cg;
- (void)createScrollViewAt:(CGRect)cg;
#end
Carousel.m
#import "Carousel.h"
#implementation Carousel
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Create a scroll view
scrollView = [[UIScrollView alloc] init];
[self addSubview:scrollView];
scrollView.delegate = (id) self;
// Init Page Control
pageControl = [[UIPageControl alloc] init];
[pageControl addTarget:self action:#selector(pageChange:) forControlEvents:UIControlEventValueChanged];
[self addSubview:pageControl];
}
return self;
}
- (IBAction)pageChange:(id)sender {
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * pageControl.currentPage;
frame.origin.y = 0;
[scrollView scrollRectToVisible:frame animated:TRUE];
NSLog(#"%i", pageControl.currentPage);
}
- (void)addView:(UIView *)view {
[scrollView addSubview:view];
}
- (void)createPageControlAt:(CGRect)cg {
pageControl.frame = cg;
}
- (void)setTotalPages:(NSUInteger)pages {
pageControl.numberOfPages = pages;
}
- (void)setCurrentPage:(NSUInteger)current {
pageControl.currentPage = current;
}
- (void)createScrollViewAt:(CGRect)cg {
[scrollView setPagingEnabled:TRUE];
scrollView.frame = cg;
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width*pageControl.numberOfPages, scrollView.frame.size.height);
[scrollView setShowsHorizontalScrollIndicator:FALSE];
[scrollView setShowsVerticalScrollIndicator:FALSE];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
float frac = scrollView.contentOffset.x / scrollView.frame.size.width;
NSUInteger page = lround(frac);
pageControl.currentPage = page;
}
#end
ViewController.m (somewhere in viewDidLoad)
Carousel *carousel = [[Carousel alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
for (int i=0; i<5; i++) {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(i * 320, 0, 320, 420)];
UIColor *color;
if(i%3==0) color = [UIColor blueColor];
else if(i%3==1) color = [UIColor redColor];
else color = [UIColor purpleColor];
view.backgroundColor = color;
[carousel addView:view];
view = nil;
}
[carousel setTotalPages:5];
[carousel setCurrentPage:0];
[carousel createPageControlAt:CGRectMake(0,420,320,40)];
[carousel createScrollViewAt:CGRectMake(0,0,320,420)];
Your code is correct. Most likely the frame of your pageControl is pretty small, so theres not a lot of area to look for touch events. You would need to increase the size of the height of pageControl in order to make sure taps are recognized all of the time.