Redraw saved in array UIBezierPaths on UIVew - ios

I'm using subclass of UIView, in order to draw shapes with UIBezierPath and touchEvents.
I'm making an app (like paint), in which I have two modes of drawing (change stroke color and width in drawRect) and when I switch between them, the already drew paths change their colors and widhts to the new ones. In order to fix this problem, when touchesEnded, I save the current Path in array and create a new one. I want to redraw the saved path(s) on the view when the new one is made, so they can be visible.
In drawRect, foreach path in my array, I do path.Stroke(), in order to visualize it, but when I switch to a certain mode of drawing, the saved paths again change their width and color.
public override void TouchesBegan (Foundation.NSSet touches, UIEvent evt)
{
base.TouchesBegan (touches, evt);
this.Path = new UIBezierPath ();
var touch = touches.AnyObject as UITouch;
var point = touch.LocationInView (this);
this.Path.MoveTo (point);
}
public override void TouchesMoved (Foundation.NSSet touches, UIEvent evt)
{
base.TouchesMoved (touches, evt);
var touch = touches.AnyObject as UITouch;
this.Path.AddLineTo(touch.LocationInView(this));
this.SetNeedsDisplay ();
}
public override void TouchesEnded (Foundation.NSSet touches, UIEvent evt)
{
base.TouchesEnded (touches, evt);
this.TouchesMoved (touches, evt);
paths.Add (Path);
}
public override void Draw (CGRect rect)
{
base.Draw (rect);
DrawSavedPaths ();
if (isScribbling)
{
this.Path.LineWidth = 2.5f;
this.PathColor.SetStroke ();
} else if (isMarking)
{
this.Path.LineWidth = 15.0f;
UIColor.FromRGBA (255, 0, 0, 0.3f).SetStroke ();
}
this.Path.Stroke ();
}
private void DrawSavedPaths()
{
if (paths.Count > 0)
{
foreach (UIBezierPath path in paths)
{
path.Stroke ();
}
}
}
Basically, in the same UIView, where I'm drawing, I want to have the previous paths drewn, preventing them from being changed.

Related

Draw method doesn't get called from the custom UIView

I'm creating a custom map pin (annotation) for iOS, draw its path and then convert the UIView to an image, using this method:
public static UIImage AsImage(this UIView view)
{
try
{
UIGraphics.BeginImageContextWithOptions(view.Bounds.Size, true, 0);
var ctx = UIGraphics.GetCurrentContext();
view.Layer.RenderInContext(ctx);
var img = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return img;
}
catch (Exception)
{
return null;
}
}
and this is the UIView-derived class, where I draw the annotation path:
public class PinView : UIView
{
public PinView()
{
ContentMode = UIViewContentMode.Redraw;
SetNeedsDisplay();
}
private readonly CGPoint[] markerPathPoints = new[]
{
new CGPoint(25f, 5f),
new CGPoint(125f, 5f),
//other points };
public override void Draw(CGRect rect)
{
base.Draw(rect);
//get graphics context
CGContext context = UIGraphics.GetCurrentContext();
//set up drawing attributes
context.SetLineWidth(10);
UIColor.White.SetFill();
UIColor.Black.SetStroke();
//create geometry
var path = new CGPath();
path.MoveToPoint(markerPathPoints[0].X, markerPathPoints[0].Y);
for (var i = 1; i < markerPathPoints.Length; i++)
{
path.AddLineToPoint(markerPathPoints[i].X, markerPathPoints[i].Y);
}
path.CloseSubpath();
//add geometry to graphics context and draw it
context.AddPath(path);
context.DrawPath(CGPathDrawingMode.FillStroke);
}
I'm modifying the Xamarin.Forms sample of the MapCustomRenderer, instead of loading the annotation from a file, I'm loading it from the view:
annotationView.Image = new PinView().AsImage();
but the PinView.Draw() never get called!
The method Draw will been invoked after the method ViewDidLoadwhen the control first been loaded , which is called automatically by the system .
In your case , you didn't set the Rect(Frame) of the PinView . So the method Draw will never been called because the Rect is zero in default .
So you could set a default frame in the constructor
public PinView()
{
Frame = new CGRect (0,0,xx,xxx);
ContentMode = UIViewContentMode.Redraw;
SetNeedsDisplay();
}
The view needs to be added to the views hierarchy, in my case, to the MKAnnotationView,
so in the CustomMKAnnotationView constructor:
public CustomMKAnnotationView(IMKAnnotation annotation, string id)
: base(annotation, id)
{
var pin = new PinView(new CGRect(0, 0, 110, 110));
pin.BackgroundColor = UIColor.White.ColorWithAlpha(0);
Add(pin);
}
and in the UIView's constructor, the Frame should be initialized as Lucas Zhang stated:
public PinView(CGRect rectangle)
{
Frame = rectangle;
ContentMode = UIViewContentMode.Redraw;
SetNeedsDisplay();
}
With these changes the Draw method is called.

Animating background image on circledimageview

I'm referring to WatchFace sample for Android Wear. The example DigitialWatchFaceWearableConfigActivity uses a color picker to change the Watch Face background. I want to use an image background on the circledimageview. What happens is the image remains the same size in both center and non-centered positions and only the black circle changes size. What do I need to implement in onCenterPosition and onNonCenterPosition in order to shrink and grow the background image?
#Override
public void onCenterPosition(boolean animate) {
if (animate) {
mShrinkAnimator.cancel();
if (!mExpandAnimator.isRunning()) {
mExpandCircleAnimator.setFloatValues(mColor.getCircleRadius(), mExpandCircleRadius);
mExpandLabelAnimator.setFloatValues(mLabel.getAlpha(), EXPAND_LABEL_ALPHA);
mExpandAnimator.start();
}
} else {
mExpandAnimator.cancel();
mColor.setCircleRadius(mExpandCircleRadius);
mLabel.setAlpha(EXPAND_LABEL_ALPHA);
}
}
#Override
public void onNonCenterPosition(boolean animate) {
if (animate) {
mExpandAnimator.cancel();
if (!mShrinkAnimator.isRunning()) {
mShrinkCircleAnimator.setFloatValues(mColor.getCircleRadius(), mShrinkCircleRadius);
mShrinkLabelAnimator.setFloatValues(mLabel.getAlpha(), SHRINK_LABEL_ALPHA);
mShrinkAnimator.start();
}
} else {
mShrinkAnimator.cancel();
mColor.setCircleRadius(mShrinkCircleRadius);
mLabel.setAlpha(SHRINK_LABEL_ALPHA);
}
}
You can use following simple code of ScaleX and ScaleY to manipulate background of onCenterPosition and onNonCenterPosition,
#Override
public void onCenterPosition(boolean b) {
image.setAlpha(1f);
image.setBackgroundResource(R.drawable.blue_oval); // This one is needed if you want to change background on center position
image.setScaleX(1.0f);
image.setScaleY(1.0f);
text.setScaleY(1.0f);
text.setAlpha(1f);
}
#Override
public void onNonCenterPosition(boolean b) {
image.setAlpha(0.5f);
image.setBackgroundResource(R.drawable.gray_oval); // This one is needed if you want to change background on non center position
image.setScaleX(0.8f);
image.setScaleY(0.8f);
text.setScaleY(0.8f);
text.setAlpha(0.5f);
}

Draw line on UIView with CAGradientLayer as backing store

I have created a UIView subclass which uses a CAGradientLayer for its layer. This is working fine. Now I want to draw a line at the end of the gradient (used as separator). I thought I can use the drawRect method for this:
public class GradientView : UIView
{
// accessors
private CAGradientLayer gradientLayer {
// read-only
get { return (CAGradientLayer)this.Layer; }
}
public CGColor[] Colors {
// set the colors of the gradient layer
get { return this.gradientLayer.Colors; }
set { this.gradientLayer.Colors = value; }
}
[Export ("layerClass")]
public static Class LayerClass ()
{
// use a different Core Animation layer for its backing store
// normally a CALayer is used for a UIView
return new Class (typeof(CAGradientLayer));
}
public override void Draw (RectangleF rect)
{
PointF pointA = new PointF (0, rect.Height);
PointF pointB = new PointF (rect.Width, rect.Height);
CAShapeLayer line = new CAShapeLayer ();
UIBezierPath linePath = new UIBezierPath ();
linePath.MoveTo (pointA);
linePath.AddLineTo(pointB);
line.Path = linePath.CGPath;
line.FillColor = null;
line.Opacity = 1.0f;
line.StrokeColor = UIColor.Black.CGColor;
this.gradientLayer.AddSublayer (line);
base.Draw (rect);
}
}
But I get a black background. Can I use drawRect for that? Or how do I add a line on it?
I'm using C# as language but you can provide your solution in Objective-C as well. Auto Layout determines the size of the GradientView and my old solution also adapted to orientation changes.
Edit:
I tried what Cyrille said:
public class GradientView : UIView
{
// accessors
private CAShapeLayer line;
private CAGradientLayer gradientLayer {
// read-only
get { return (CAGradientLayer)this.Layer; }
}
public CGColor[] Colors {
// set the colors of the gradient layer
get { return this.gradientLayer.Colors; }
set { this.gradientLayer.Colors = value; }
}
public GradientView()
{
PointF pointA = new PointF (0, this.Bounds.Height);
PointF pointB = new PointF (this.Bounds.Width, this.Bounds.Height);
line = new CAShapeLayer ();
UIBezierPath linePath = new UIBezierPath ();
linePath.MoveTo (pointA);
linePath.AddLineTo(pointB);
line.Path = linePath.CGPath;
line.FillColor = null;
line.Opacity = 1.0f;
line.StrokeColor = UIColor.Black.CGColor;
line.LineWidth = 1.0f;
this.gradientLayer.AddSublayer (line);
}
[Export ("layerClass")]
public static Class LayerClass ()
{
// use a different Core Animation layer for its backing store
// normally a CALayer is used for a UIView
return new Class (typeof(CAGradientLayer));
}
public override void LayoutSubviews ()
{
PointF pointA = new PointF (0, this.Bounds.Height-2);
PointF pointB = new PointF (this.Bounds.Width, this.Bounds.Height-2);
UIBezierPath linePath = new UIBezierPath ();
linePath.MoveTo (pointA);
linePath.AddLineTo(pointB);
line.Path = linePath.CGPath;
base.LayoutSubviews ();
}
}
This seems to do what I want except that the line is not a thin hairline. Is CAShapeLayer inappropriate for this?
Solution:
Of course it would be possible to use CAShapeLayer, but if I have to setup things in drawRect, then I completely draw my line there. Now the solution for me does look like the following:
public class GradientView : UIView
{
// accessors
private CAShapeLayer line;
private CAGradientLayer gradientLayer {
// read-only
get { return (CAGradientLayer)this.Layer; }
}
public CGColor[] Colors {
// set the colors of the gradient layer
get { return this.gradientLayer.Colors; }
set { this.gradientLayer.Colors = value; }
}
public GradientView()
{
this.BackgroundColor = UIColor.Clear;
}
[Export ("layerClass")]
public static Class LayerClass ()
{
// use a different Core Animation layer for its backing store
// normally a CALayer is used for a UIView
return new Class (typeof(CAGradientLayer));
}
public override void Draw (RectangleF rect)
{
base.Draw (rect);
// get graphics context
CGContext context = UIGraphics.GetCurrentContext ();
// start point
context.MoveTo (0, rect.Height);
// end point
context.AddLineToPoint (rect.Width, rect.Height);
context.SetLineWidth (1.0f);
context.SetShouldAntialias(false);
// draw the path
context.DrawPath (CGPathDrawingMode.Stroke);
}
Using a CAGradientLayer as the backing layer of your view does not prevent you from adding another CAShapeLayer inside your view upon initialization. Then you can use this shape layer to draw your straight line, by setting its path property accordingly (and recalculate it any time the bounds change, in -layoutSubviews, to react correctly to orientation/layout changes).

XNA mouse 'position relative-to' changes every build

Okay so I'm starting to make a main menu for a small flash game and to do this I want to use the mouse to click on buttons etc. I have a button class in which I create two rectangles: a rectangle for the button and a rectangle for the mouse based on its X and Y, 1 pixel by 1 pixel. I use Rectangle.Intersects to check if they are touching before seeing if left mouse button is down. Problem is, the thing the mouse position is relative to changes every time so no matter where the mouse button is on the screen, it's never the same co-ordinates as in a different build in that exact same position. I seriously just need ideas now as I'm running out. If I didn't explain it very well or you need further details to help please ask - I WOULD BE SO GRATEFUL.
Will post back if I find an answer
Update - Okay here's the button class
class OnScreenButton
{
public Texture2D texture;
Vector2 position;
Rectangle rectangle;
Color colour = new Color(255, 255, 255, 255);
public Vector2 size;
public OnScreenButton(Texture2D newtexture, GraphicsDevice graphics)
{
texture = newtexture;
// ScreenW = 500, ScreenH = 600
// Img W = 80, Img H = 20
size = new Vector2(graphics.Viewport.Width / 10, graphics.Viewport.Height / 30);
size = new Vector2(texture.Width, texture.Height);
}
bool down;
public bool isClicked;
public void Update(MouseState mouseState)
{
rectangle = new Rectangle((int)position.X, (int)position.Y, (int)size.X, (int)size.Y);
Rectangle mouseRectangle = new Rectangle(mouseState.X, mouseState.Y, 1, 1);
if (mouseRectangle.Intersects(rectangle))
{
if (colour.A == 255)
{
down = false;
}
if (colour.A == 0)
{
down = true;
}
if (down)
{
colour.A += 3;
}
else
{
colour.A -= 3;
}
if (mouseState.LeftButton == ButtonState.Pressed)
{
isClicked = true;
}
}
else if (colour.A < 255)
{
colour.A += 3;
isClicked = false;
colour.A = (255);
}
}
public void SetPosition(Vector2 newPos)
{
position = newPos;
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(texture, rectangle, colour);
}
}
}
(Sorry for weird formatting, brand new to stack overflow and the posting is still a little confusing)
Here is some other code I think is relevent...
Game.1 initializing stuff
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
protected override void Initialize()
{
// TODO: Add your initialization logic here
Mouse.WindowHandle = Window.Handle;
base.Initialize();
}
public Main()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
KeyboardState keyboardState;
MouseState mouseState;
Main menu update routine...
private void UpdateMainMenu(GameTime gameTime)
{
// Button options
if (buttonPlay.isClicked == true)
{
CreateNewGame();
currentGameState = GameState.playing;
}
buttonPlay.Update(mouseState);
if (buttonExit.isClicked == true)
{
this.Exit();
}
buttonExit.Update(mouseState);
// Press enter to play
if (keyboardState.IsKeyDown(Keys.Enter))
{
CreateNewGame();
currentGameState = GameState.playing;
}
}
Here's thee draw routine for main menu...
public void DrawMainMenu()
{
spriteBatch.Draw(mainMenuBackground, new Vector2(0, 0), Color.White);
buttonPlay.Draw(spriteBatch);
buttonExit.Draw(spriteBatch);
spriteBatch.DrawString(playerAmmoFont, String.Format("{0}", mouseState), new Vector2(0, 0), Color.White);
}
okay that's all I can think of
UPDATE - Okay so I know a few things that aren't the problem...
The whole of my button class is fine, I made a new project and inserted all the relevant code into it and it worked absolutely perfectly so I'm starting to think its something to do with the code positioning and the graphics device stuff although I still don't have a clue how to fix it.
the window appears at the same spot every time
there is no pattern to the change in coordinates at all
this is really annoying
UPDATE - OKAY. I spent a long time writing down the coordinates that I got each time I ran the code and stuck to cursor in the top right corner of the screen. Here is what I got.
(-203, -225)
(-253, -275)
(-53, -75)
(-103, -125)
(-153, -175)
(-203, -225)
(-253, -275)
(-53, -75)
(-103, -125)
(-153, -175)
(-203, -225)
(-253, -275)
(-53, -75)
(-103, -125)
(-153, -175)
(-203, -225)
(-253, -275)
(-53, -75)
(-103, -125)
(-153, -175)
(-203, -225)
(-78, -100)
(-128, -150)
(-178, -200)
(-228, -250)
(-28, -50)
(-53, -75)
(-103, -125)
(-153, -175) < AND FROM HERE THE PATTERN LOOPS ROUND.
I just don't get how the same code can execute a different bug on different executions like this.
Also, mouse.Wheel doesn't go up or down whereas it works on the project that I made to test the relevant code where the mouse position was relevant to the top left pixel of the game window.
UPDATE - EVEN MORE DAMN COMPLICATIONS - So I just rand it a few times again and the offset values are offset... the increase is the same but I got values like (-178, -200) then (-228, -250). I have also discovered that the mouse is not relative to the game window what so ever, if I jam the mouse in the top right corner of the screen and check the coordinates, then move the game window and do the same again, the coordinates don't change. Please please please help me, or tell me if I'm being stupid, or something. Thanks.
The mouse coordinates are relative to the monitor. Here is my general button class to try and work for your situation.
public class Button
{
public event EventHandler<EventArgs> Clicked;
public Vector2 Position { get; set;}
public Texture2D Texture { get; set;}
public Color Tint { get; set; }
public float Scale { get; set; }
public float Rotation { get; set; }
public int Width
{
get
{
if (texture == null)
return 0;
else
return texture.Width;
}
}
public int Height
{
get
{
if (texture == null)
return 0;
else
return texture.Height;
}
}
private void OnClick()
{
if (Clicked != null)
Clicked(this, EventArgs.Empty);
}
public Button(Vector2 position, Texture2D texture)
: base(parent)
{
Position = position;
Texture = texture;
Tint = Color.White;
Scale = 1.0f;
Rotation = 0.0f;
}
public bool HandleClick(Vector2 vector)
{
if (vector.X >= Position.X)
{
if (vector.X <= Position.X + Width)
{
if (vector.Y >= Position.Y)
{
if (vector.Y <= Position.Y + Height)
{
OnClick();
return true;
}
}
}
}
return false;
}
public bool HandleEntered(Vector2 vector)
{
if (vector.X >= Position.X)
{
if (vector.X <= Position.X + Width)
{
if (vector.Y >= Position.Y)
{
if (vector.Y <= Position.Y + Height)
{
return true;
}
}
}
}
return false;
}
public override void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(Texture, Position, null, Tint, Rotation, Vector2.Zero, Scale, SpriteEffects.None, 0.0f);
}
Declare a button:
Button btn = new Button(position where you want the button, texture for the button);
btn.Clicked += () => { /* Handle button clicked code here */ };
In your update method inside your main game:
public void Update (GameTime gameTime)
{
MouseState mouseState = Mouse.GetState();
if(mouseState.LeftButton == ButtonState.Pressed) // check if mouse is clicked
{
btn.HandleClicked(new Vector2(mouseState.X, mouseState.Y)); // If true then the button clicked event will fire
// Here you can also change the color of the button if the button is currently clicked
}
// Here you can change the color of the button if the mouse is hover over the control
// Example:
btn.Tint = btn.HandleEntered(new Vector2(mouseState.X, mouseState.Y)) ? Color.White * 0.75f : Color.White;
}
Note: You can also use a rectangle for the button to adjust its size instead of strictly using the textures dimensions. Hope this gives some insight.
So here's what was going on: I had a bullet class in my game for every bullet shot. In this class I check whether the bullets hits nothing, hits the asteroid, or destroys the asteroids. If the latter is true then I would increment playerScore by 5. PlayerScore was a Game1 attribute so I thought the easiest way to do this would be to create a new Game1 in bullet.cs to allow me to refer to the variable. Deleting the "Main mainGame = new Main():" in Bullet.cs fixed this issue and I think the issue was coming from a new graphics device being made every single time I fired a single bullet.

How do I use a image photo as a Windows phone Button

I created a button image and I named it Texture2D btn_play. It is for a main menu and I want to press it to change the CurrentGameState.
My variables:
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
Texture2D Tex_Btn_play;
Rectangle Rec_Btn_play;
enum Menu
{
MainMenu,
Playing,
Exit,
}
Menu CurrentGameState = Menu.MainMenu;
And the Update method:
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
if (Rec_Btn_play = TouchLocationState.Pressed);
// TODO: Add your update logic here
base.Update(gameTime);
}
The if (Rec_Btn_play = TouchLocationState.Pressed); Is wrong, I don't know why. Please help me solve this problem.
You need to find the touch locations and check if the rectangle intersects with that position
TouchCollection touchCollection = TouchPanel.GetState();
foreach (TouchLocation tl in touchCollection)
{
if (tl.State == TouchLocationState.Pressed)
{
if (Rct_Btn_Play.Contains(new Point(tl.Position.X,tl.Position.Y))
{
//DoStuff
}
}
}

Resources