Hi how do i create a moving ball on iOS.
When the program starts, I will display the ball on the left of the screen. Subsequently, each time i click a UIButton, how do i move the ball to the right on the same x-axis.
I managed to display the ball but how do i update and redraw its location when the UIButton is pressed? currently each time i press the UIButton, it creates a new ball and the old balls are not cleared.
i understand its because i recreate a new instance of the ball. So how shall i go about fixing this?
Here is my code....
ballView.m
#implementation BallView
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setBackgroundColor:[UIColor clearColor]];
}
return self;
}
-(void)drawRect:(CGRect)dirtyRect
{
NSLog(#"in drawRect");
// Get the graphics context and clear it
CGContextRef ctx = UIGraphicsGetCurrentContext();
// Draw a solid ball
CGContextSetRGBFillColor(ctx, 0, 0, 0, 1);
CGContextFillEllipseInRect(ctx, dirtyRect);
[self setNeedsDisplay];
}
-(BOOL)canBecomeFirstResponder
{
return YES;
}
MoveBallController.m
-(void)moveBall
{
CGRect viewFrame = CGRectMake(0, 0, 30, 30);
BallView *ball = [[BallView alloc] initWithFrame:viewFrame];
[[self dotView] addSubview:ball];
}
Create one BallView instance and store it in an instance variable. Then when the button is clicked you simply update the ball's frame:
- (void)viewDidLoad {
[super viewDidLoad];
CGRect viewFrame = CGRectMake(0, 0, 30, 30);
_ball = [[BallView alloc] initWithFrame:viewFrame];
[[self dotView] addSubview:_ball];
}
- (void)moveBall {
CGRect frame = _ball.frame;
frame.origin.x += 5; // use some appropriate increment
_ball.frame = frame;
}
where _ball is your new instance variable.
Related
In my project, there’s a main View Controller with a subview that contains a Sprite Kit scene. The subview (“MySubview”) is implemented like this:
- (id) initWithFrame: (CGRect) frame {
self = [super initWithFrame: frame];
if (self) {
myScene* scene = [myScene sceneWithSize: CGSizeMake(WIDTH, HEIGHT)];
[self presentScene: scene];
}
return self;
}
The main View Controller initialises this subview like this:
- (void) viewDidLoad {
[super viewDidLoad];
_mySubview = [[MySubview alloc] initWithFrame: CGRectMake(X, Y, WIDTH, HEIGHT)];
[self.view addSubview: mySubview];
}
All this is good and working so far. But then I try adding Core Animation on an object outside of the Sprite Kit view. I add the following to my main View Controller’s implementation:
CAShapeLayer* circleLayer;
- (void) setupCircle {
CGPathRef circlePath = CGPathCreateWithEllipseInRect(CGRectMake(CIRCLE_X, CIRCLE_Y, CIRCLE_WIDTH, CIRCLE_HEIGHT), nil);
circleLayer = [CAShapeLayer layer];
circleLayer.path = circlePath;
circleLayer.fillColor = [UIColor orangeColor].CGColor;
circleLayer.strokeColor = [UIColor blueColor].CGColor;
circleLayer.lineWidth = 10;
[self.view.layer addSublayer: circleLayer];
}
- (void) animateCircle {
CABasicAnimation* circleAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
circleAnimation.duration = 4;
circleAnimation.fromValue = #0;
circleAnimation.toValue = #1;
[circleLayer addAnimation: circleAnimation
forKey: #"circleAnimation"];
}
And then I change my viewDidLoad method to:
- (void) viewDidLoad {
[super viewDidLoad];
_mySubview = [[MySubview alloc] initWithFrame: CGRectMake(X, Y, WIDTH, HEIGHT)];
[self.view addSubview: mySubview];
[self setupCircle];
[self animateCircle];
}
This draws the circle, but does not animate its stroke. When I comment out all the Sprite Kit subview code, it does animate. Is it a problem with threading, run loops, or something else? I’m stuck here. Thanks for the help.
I have no idea what is going on.
I am not doing anything special.
I have a UIViewController that I create programmatically, no storyboard.
Then I create a UIView with nothing special
#import "SettingsView.h"
#interface SettingsView()
#property UIImageView* bg;
#end
#implementation SettingsView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.bg = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"happy.png"]];
[self addSubview:self.bg];
}
return self;
}
-(void)layoutSubviews
{
self.bg.frame = self.frame;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
I instantiate it in the VC and when I want to show it I do:
-(void)showSettingsView
{
self.settings.frame = self.view.frame;
self.btnInfo.userInteractionEnabled = NO;
self.btnPause.userInteractionEnabled = NO;
self.btnSettings.userInteractionEnabled = NO;
self.view.userInteractionEnabled = NO;
[self.view addSubview:self.settings];
CGRect frame = self.settings.frame;
frame.origin.x = frame.size.width * 0.48;
frame.origin.y = 0;
self.settings.frame = frame;
}
Instead of seeing the image view roughly half way on the screen, it starts at exactly double (0.48 * 2 = 96%) of the screen.
I cannot understand why coordinates are doubled or something?
When I check the frame, I clearly see the origin at around 150 which is half the width of the iPhone.
What could be happening?
Thanks
Could it be that in...
-(void)layoutSubviews
{
self.bg.frame = self.frame;
}
you should be using bounds instead of frame of your self object...
-(void)layoutSubviews
{
self.bg.frame = self.bounds;
}
I have some fairly complex layers in a part of my app and I want them to rasterize as their content changes close to never. The frame of the view these layers are in can be changed by dragging a "spacerbar" around with your finger.
I made a simple test-app to visualise my problem and show you some code:
#interface ViewController () {
UIView* m_MainView;
UIView* m_TopView;
UIView* m_BottomView;
UIView* m_MidView;
int m_Position;
}
#end
#implementation ViewController
- (void)loadView {
m_MainView = [[UIView alloc] init];
m_TopView = [[UIView alloc] init];
m_TopView.backgroundColor = UIColor.blueColor;
m_TopView.translatesAutoresizingMaskIntoConstraints = false;
m_BottomView = [[UIView alloc] init];
m_BottomView.backgroundColor = UIColor.blueColor;
m_BottomView.translatesAutoresizingMaskIntoConstraints = false;
m_MidView = [[UIView alloc] init];
m_MidView.backgroundColor = UIColor.blackColor;
m_MidView.translatesAutoresizingMaskIntoConstraints = false;
[m_MainView addSubview:m_TopView];
[m_MainView addSubview:m_BottomView];
[m_MainView addSubview:m_MidView];
CALayer* targetLayer = [[CALayer alloc] init];
targetLayer.frame = CGRectMake (100, 200, 500, 50);
targetLayer.backgroundColor = UIColor.yellowColor.CGColor;
targetLayer.shouldRasterize = true;
[m_BottomView.layer addSublayer:targetLayer];
UIPanGestureRecognizer* recognizer = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:#selector(handlePan:)];
[m_MainView addGestureRecognizer:recognizer];
m_Position = 400;
super.view = m_MainView;
}
- (void)handlePan:(UIPanGestureRecognizer*)sender {
float touchY = [sender locationInView:m_MainView].y;
m_Position = (int)touchY;
[m_MainView setNeedsLayout];
}
- (void)viewWillLayoutSubviews {
CGRect bounds = super.view.bounds;
m_TopView.frame = CGRectMake (0, 0, bounds.size.width, m_Position - 10);
m_MidView.frame = CGRectMake (0, m_Position - 10, bounds.size.width, 20);
m_BottomView.frame = CGRectMake (0, m_Position + 10, bounds.size.width,
bounds.size.height - m_Position - 10);
}
#end
.
Now ... By using instruments and activating "Color Hits Green and Misses Red" one can see when a layer is redrawn (is then highlighted in red).
In this example the bar (targetLayer) is green (cached) most of the time, but if I start dragging the spacer (which in fact changes the frame of the bottom-view) the layer turns red some times ... especially if I reverse the direction of the drag or if I drag slowly.
In my app this causes flickering as redrawing these layers is expensive.
Why does this happen??
I never change any property of the layer but it gets redrawn anyways?
I am sure I am missing something :-)
As a workaround I could make a snapshot of the layers and use the image ... that would work I think and if there is no solution I will have to use this approach but I would like to understand what is the problem here.
Anyone?
I made a Custom Circle Control, Were i am Sending values by Slider from my RootView Controller. Can You help me out, how to Apply touches to Circle Control were i can change the values, like making custom slider around my Custom Circle.
#import "RKCustomCircle.h"
#implementation RKCustomCircle
#synthesize sliderPerccentageValue;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
xPos = 320/2;
yPos = 250;
radius = 80;
rotationAngle = 0;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
[self drawCircleChart: context];
}
- (void) drawCircleChart:(CGContextRef) context
{
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextFillRect(context, CGRectMake(0, 0, 320, 480));
float a = rotationAngle;
[self drawCirclewithStartingAngle:a withContext:context];
}
- (void) drawCirclewithStartingAngle:(float)startAngle withContext:(CGContextRef) context
{
float endAngle = startAngle + (sliderPerccentageValue/ 100) * (M_PI*2);
float adjY = yPos;
float rad = radius;
CGContextSetFillColorWithColor(context, [UIColor greenColor].CGColor);
CGContextSetStrokeColorWithColor(context,[UIColor blackColor].CGColor);
CGContextSetLineWidth(context, 1.0);
CGContextBeginPath(context);
CGContextMoveToPoint(context, xPos, adjY);
CGContextAddArc(context, xPos, adjY, rad, startAngle, endAngle, 0);
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke);
}
RootView is,
#implementation RKViewController
- (void)viewDidLoad
{
[super viewDidLoad];
circleView = [[RKCustomCircle alloc] initWithFrame:CGRectMake(0, 100, 320, 360)];
[circleView addTarget:self action:#selector(newValue:) forControlEvents:UIControlEventValueChanged];
[self.view addSubview:circleView];
}
- (IBAction)sliderValueChangedInPercentage:(UISlider *)sender {
circleView.sliderPerccentageValue =sender.value;
[circleView setNeedsDisplay];
}
Use UITapGestureRecognizer.
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(oneTap:)];
[singleTap setNumberOfTapsRequired:1];
[singleTap setNumberOfTouchesRequired:1];
[circleView addGestureRecognizer:singleTap];
Add this method
- (void)oneTap:(UIGestureRecognizer *)gesture
{
NSLog(#"Touch occur");
}
All your things are correct, but only the following part is incorrect.
- (IBAction)sliderValueChangedInPercentage:(UISlider *)sender {
circleView.sliderPerccentageValue =sender.value;
[circleView setNeedsDisplay];
}
The above code doesn't supply any value because you have attached to your control that implements UIControl, not UISlider. You don't have to either, your UIControl will work..
Instead you can have touch events inside your control itself no need to add any action from outside, and you have to detect circular touch and keep track of values, see this app here it will help you to get values when touched circular.
Hope it will help you. All the best.
You need to override the pointInside:withEvent: method of the circle view. Refer to this for more information.
I am trying to rotate the rectangle around the circle. So far after putting together some code I found in various places (mainly here: https://stackoverflow.com/a/4657476/861181) , I am able to rotate rectangle around it's center axis.
How can I make it rotate around the circle?
Here is what I have:
OverlaySelectionView.h
#import <QuartzCore/QuartzCore.h>
#interface OverlaySelectionView : UIView {
#private
UIView* dragArea;
CGRect dragAreaBounds;
UIView* vectorArea;
UITouch *currentTouch;
CGPoint touchLocationpoint;
CGPoint PrevioustouchLocationpoint;
}
#property CGRect vectorBounds;
#end
OverlaySelectionView.m
#import "OverlaySelectionView.h"
#interface OverlaySelectionView()
#property (nonatomic, retain) UIView* vectorArea;
#end
#implementation OverlaySelectionView
#synthesize vectorArea, vectorBounds;
#synthesize delegate;
- (void) initialize {
self.userInteractionEnabled = YES;
self.multipleTouchEnabled = NO;
self.backgroundColor = [UIColor clearColor];
self.opaque = NO;
self.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(rotateVector:)];
panRecognizer.maximumNumberOfTouches = 1;
[self addGestureRecognizer:panRecognizer];
}
- (id) initWithCoder: (NSCoder*) coder {
self = [super initWithCoder: coder];
if (self != nil) {
[self initialize];
}
return self;
}
- (id) initWithFrame: (CGRect) frame {
self = [super initWithFrame: frame];
if (self != nil) {
[self initialize];
}
return self;
}
- (void)drawRect:(CGRect)rect {
if (vectorBounds.origin.x){
UIView* area = [[UIView alloc] initWithFrame: vectorBounds];
area.backgroundColor = [UIColor grayColor];
area.opaque = YES;
area.userInteractionEnabled = NO;
vectorArea = area;
[self addSubview: vectorArea];
}
}
- (void)rotateVector: (UIPanGestureRecognizer *)panRecognizer{
if (touchLocationpoint.x){
PrevioustouchLocationpoint = touchLocationpoint;
}
if ([panRecognizer numberOfTouches] >= 1){
touchLocationpoint = [panRecognizer locationOfTouch:0 inView:self];
}
CGPoint origin;
origin.x=240;
origin.y=160;
CGPoint previousDifference = [self vectorFromPoint:origin toPoint:PrevioustouchLocationpoint];
CGAffineTransform newTransform =CGAffineTransformScale(vectorArea.transform, 1, 1);
CGFloat previousRotation = atan2(previousDifference.y, previousDifference.x);
CGPoint currentDifference = [self vectorFromPoint:origin toPoint:touchLocationpoint];
CGFloat currentRotation = atan2(currentDifference.y, currentDifference.x);
CGFloat newAngle = currentRotation- previousRotation;
newTransform = CGAffineTransformRotate(newTransform, newAngle);
[self animateView:vectorArea toPosition:newTransform];
}
-(CGPoint)vectorFromPoint:(CGPoint)firstPoint toPoint:(CGPoint)secondPoint
{
CGPoint result;
CGFloat x = secondPoint.x-firstPoint.x;
CGFloat y = secondPoint.y-firstPoint.y;
result = CGPointMake(x, y);
return result;
}
-(void)animateView:(UIView *)theView toPosition:(CGAffineTransform) newTransform
{
[UIView setAnimationsEnabled:YES];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.0750];
vectorArea.transform = newTransform;
[UIView commitAnimations];
}
#end
here is attempt to clarify. I am creating the rectangle from a coordinates on a map. Here is the function that creates that rectangle in the main view. Essentially it is the middle of the screen:
overlay is the view created with the above code.
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
if (!circle){
circle = [MKCircle circleWithCenterCoordinate: userLocation.coordinate radius:100];
[mainMapView addOverlay:circle];
CGPoint centerPoint = [mapView convertCoordinate:userLocation.coordinate toPointToView:self.view];
CGPoint upPoint = CGPointMake(centerPoint.x, centerPoint.y - 100);
overlay = [[OverlaySelectionView alloc] initWithFrame: self.view.frame];
overlay.vectorBounds = CGRectMake(upPoint.x, upPoint.y, 30, 100);
[self.view addSubview: overlay];
}
}
Here is the sketch of what I am trying to achieve:
Introduction
A rotation is always done around (0,0).
What you already know:
To rotate around the center of the rectangle you translate the rect to origin, rotate and translate back.
Now for your question:
to rotate around a center point of a circle, simply move the center of the rectangle such that the circle is at (0,0) then rotate, and move back.
start positioning the rectangle at 12 o clock, with the center line at 12.
1) as explained you always rotate around 0,0, so move the center of the circle to 0,0
CGAffineTransform trans1 = CGAffineTransformTranslation(-circ.x, -circ.y);
2) rotate by angle
CGAffineTransform transRot = CGAffineTransformRotation(angle); // or -angle try out.
3) Move back
CGAffineTransform transBack = CGAffineTransformTranslation(circ.x, circ.y);
Concat these 3 rotation matrices to one combibed matrix, and apply it to the rectangle.
CGAffineTransformation tCombo = CGAffineTransformConcat(trans1, transRot);
tCombo = CGTransformationConcat(tCombo, transback);
Apply
rectangle.transform = tCombo;
You probably should also read the chapter about Transformation matrices in Quartz docu.
This code is written with a text editor only, so expect slighly different function names.