I am working on iOS application, there is a requirement that I have to make a wheel, with this wheel, app will get wheel image from server, then user is able to tap a button to rotate it.
Input is wheel image and specific angle, I have to stop wheel at that angle!
Beside of that, the wheel should have velocity while rotating.
In case you have any ideas or solution, please share to me.
Thanks a lot!
To create this view it is simplest to have a square UIView which clips subviews (is a property on UIView) and has a corner radius (property on a layer view.layer.cornerRadius) set to half the view width (or height). On this view add an image view with the image provided.
To rotate the view you can simply use transform like view.transform = CGAffineTransform(rotationAngle: angleInRadians). This property is animatable so you can use UIView.animate... methods to animate the rotation. For this case all you need to do is is set the transform inside the animation block to the target angle and view will rotate correctly and will animate. You may also add some options for instance the curve could be ease-in-out which will make wheel start slowly, then accelerate, then slow down and stop.
If your case is that your wheel may make a few circles before stopping at a certain angle then the UIView animations may be a bit inappropriate. In that case I would create a repeating timer with interval of 1.0/60.0 so it will trigger 60FPS. When your wheel starts you would save a start date:
startDate = Date()
and end date:
endDate = startDate.addingTimeInterval(expectedDuration)
You also need to save the start angle:
startAngle = currentAngle
and end angle like:
endAngle = CGFloat(Int(currentAngle/(CGFloat.pi*2))*(CGFloat.pi*2)) + // This will roughly return how many circles have currently pass
CGFloat.pi*(numberOfFullCircles*2) + // Add full circles
angleToStopAtInRadians // Add the destination angle
Now whenever the timer triggers you need to update the transform of the view to the target rotation. You need to find where in the animation you currently are depending on the current time:
let absoluteScale = endDate.timeIntervalSince(Date())/endDate.timeIntervalSince(startDate)
let scale = CGFloat(max(0.0, min(absoluteScale, 1.0)))
Then we need to use linear interpolation:
currentAngle = startAngle + (endAngle-startAngle)*scale // Use this in the rotation matrix
To use non linear (ease out for instance) it is best to modify the scale. For ease out you may for instance use scale = pow(scale, 1.0/1.3). You may play around with these values as you wish or find/create some more interesting algorithms.
Then the last thing that you need is to stop the timer when the animation has ended which is already written in absoluteScale: if absoluteScale >= 1.0 then the animation is complete and you should invalidate the timer and trigger whatever needs to execute after it.
Have fun with this and when you find yourself in trouble please post a new question with some details and code.
Related
I am working on an app, with UISlider and need to make it circular.
Is it possible via some native code, or does a 3rd party library have to be integrated?
I want to fill color as user will slider over slider.
You can find 3rd party libraries for circular slider.
Take a look into these:
https://www.cocoacontrols.com/controls/uicircularslider
https://github.com/thomasfinch/Circular-UISlider
https://github.com/milianoo/CurvySlider
I came across this question in a seperate thread with multiple links and didn't like any of the answers offered so I thought I would add my own in what seemed to be the most relevant thread, on the off chance someone else finds their way here and likes the option I provide.
it is not elegant or simple but it is easy and I thought it would be a nice starting point for others to develop their own.
in this example the circular uiScroller is placed on a view (or HUD) that is presented over the top of an SKView. I then use a hitTest to transfer any touches from the gameScene to the UIView and use _touchesBegan and _touchesMoved to update a series of buttons that are hidden like so:
the arrows are all hidden but we use the frames of each box to register where the touch is
I made the arrows UIButtons so it was easier to use the storyboard but I think an imageView works fine as we are only checking the frame of the arrow inside _touchesBegan and then sending it to the function rather than collection a touch event from the arrow itself.
if upBtn.frame.contains(touch) {
upBtnPress() //if you made this a touch func like me a call to (self) as the sender may be required
}
the black circle itself remains visible so it looks like this is where your taps are going.
then finally you create multiple images like this:
any circular pattern for each location possible is appropriate
(I had a total of 16 points for mine)
and then under any press function you just change the image of the black circle to show the correct image based on where the touch location is and you're done!
circleDpad.image = UIImage(named: "up")
calculating the angle:
is quite very simple, there are 16 points on my dial so I need to convert this into a possible direction in 1...360
func angleChanged(angle: CGFloat) {
let valueReached = (angle / 16) * 360 // this gives you the angle in degrees, converting any point that's x/16 into x/360
let turret = checkBaseSelect() //this is my personal code
turret?.tAngle = CGFloat(valueReached) * (CGFloat.pi / 180) //this converts degrees to radians and applies it where I want it.
}
despite only having 16 points I found that even without applying animation it looks quite smooth. thanks for reading.
this is a gif of my circular slider in action
This is my first ever guide, if it needs more information (or should be somewhere else) please let me know.
I'm using a variant of this code to slighty rotate a UIButton about its center:
CGFloat jiggleAngle = (-M_PI * 2.0) * (1.0 / 64.0);
self.transform = CGAffineTransformRotate(self.transform, jiggleAngle);
This code generally works as expected and rotates the button in-place, about 12 degrees counter-clockwise. However, this only works if I do not reposition the button inside my layoutSubviews method. If I do any repositioning of the button at all from its initially laid-out location, the attempt to rotate above results in the button's disappearance. If the angle I choose for rotation is an exact multiple of 90 degrees, the rotation works somehow even after a move in layoutSubviews.
I understand that my button's transform is being altered in layoutSubviews and this results in the subsequent weirdness when I attempt to rotate it with the above code. I have currently worked around this problem by placing the button I wish to rotate inside another UIView and then moving that view around as desired, but I'd like a better solution that doesn't require redoing my screen layouts.
How can I adjust/alter my button's transform after a move, so that this rotation code continues to work as expected?
The problem you are facing is that a view's frame is "undefined" if it's transform isn't the identity transform.
What you should do is use the view's center property to move it around. That works even if you've changed it's transform.
You can also apply a rotation to the view's layer instead of the view (although be aware that the layer transform is a CATransform3D, a 4x4 transformation matrix instead of a 3x3, so you need different methods to manipulate it.)
Is it possible to rotate UILabels or similar about a clockface?
For example, if I had an array of numbers from one to twelve, it would render as such in a circular manner. However, with a touch event I could move the labels about the face of the clock i.e. I could move 12 in a rightward motion until it reached 6, with all other numbers remaining in their positions.
I have considered UIPicker, but that follows a 'top to bottom' approach. I need a solution to move the label about a point while maintaining the order of the other labels.
It's simply a matter of opposing rotation transforms. Rotate the clock face while at the same time rotating all the subview numbers the same amount in the opposite direction.
Recommended:
1 Clock without small & Big handle (As Image) set it in ImageView.
2 Another two image view one for Small hand another one for Big hand.
3 Rotate the hands(UIImageView) by using the below code as you needed.
#define DEGREES_TO_RADIANS(angle) ((angle) / 180.0 * M_PI)
UIImageView *smallHand = set your smallHand image view here;
UIImageView *bigHand = set your bigHand image view here;
// Small hand
CGAffineTransform smallHandTransform = CGAffineTransformRotate(self.transform, (DEGREES_TO_RADIANS(1)));
// Big hand
CGAffineTransform bigHandTransform = CGAffineTransformRotate(self.transform, (DEGREES_TO_RADIANS(1*0.60)));
// Apply rotation
smallHand.transform = smallHandTransform;
bigHand.transform = bigHandTransform;
I knew that question was rotating the label, if you need it apply this transform on UILabel.
I hope somebody will make use of this idea. Good luck :)
Here is the example project
I'm wondering if the following is possible with CoreAnimation.
Like the title says, I'd like to animate or seek if you will, to a position in time over an entire timeline animation.
For example, say I create a CABasicAnimation which animates a CALayer's color from blue to red over an entire timeline of 10 seconds with an easing function of EaseOut.
Rather than playing that animation from start to finish, I'd prefer finer control.
For example, I'd like to say animateTo(50%) of the entire animation. This would result in an animation starting at 0% and animating to 50% of the entire animation with an ease out function. So the animation would last 5 seconds and we'd be in the middle of our color transform.
After this I could say, animateTo(20%). This would result in an animation starting from our current position of 50% and animating in reverse to 20% of the total animation. So we'd end up with a 3 second animation and we'd be 20% into our color transform from blue to red.
In the end we're just animating to a position in time over the entire timeline. I could easily say animateTo(5 seconds) rather than animateTo(50%). Or animateTo(2 seconds) rather than animateTo(20%).
I've been reading up on CoreAnimation and it appears I am un-able to get this kind of control. Has anyone else tried this with CoreAnimation?
Thanks!
The latest SDK adds a bit to UIKit animation.
It might suit your needs...
According to the documentation:
New object-based, fully interactive and interruptible animation support that lets you retain control of your animations and link them with gesture-based interactions
The new shiny UIViewPropertyAnimator:
let animator = UIViewPropertyAnimator(duration: 3.0, curve: .easeInOut) {
self.myView.alpha = 0
}
animator.startAnimation()
animator.pauseAnimation()
animator.isReversed = true
animator.fractionComplete = 1.5 / CGFloat(animator.duration)
However there are some limitations:
The PropertyAnimator requires targeting iOS 10 (which is still in beta as at the time of writing).
Also the animations are limited to UIView Animatable Properties, which are:
frame
bounds
center
transform
alpha
backgroundColor
contentStretch
If you seek more flexibility and features take a look at some open source libraries like TRX and Advance:
import TRX
let tween = Tween(from: 0, to: 1, time:3, ease:Ease.Quad.easeOut) {
anyObject.anyProperty = $0
}
let reversed = tween.reversed()
reversed.seek(1.5)
reversed.start()
As already pointed out, there is no built-in function. But if you want to create your own solution, here are a few notes and useful functions that are already there.
The CABasicAnimation conforms to the `CAMediaTiming´ protocol and contains some neat functionality for your use case:
timeOffset - this value determines at what time into the animation you actually want to start, if you have a animation with a duration of 2 seconds and timeOffset set to 1, it will cause the animation to start in the middle.
speed - setting that value to -1 would cause the animation to reverse (e.g. for the case of from 50% to 20%)
duration - might be changed if you want to change the duration of the animation for shorter changes e.g. form 40% to 50%.
timingFunction - I was hoping it might be possible to provide a custom timing function here, which for example just completes the animation after 1 second even if the duration is set to be 5 seconds, meaning: a linear function between 0 and 1 and after that just constant. It may however be possible to achieve an approximation of that with a bezierPath as well.
I have a scene where my gameplay happens. I'm looking for a way to slowly 'zoom-out' so more and more space becomes visible as the time passes. But I want the HUD and some other elements to stay the same size. It's like the mouse wheel functionality in top-down games.
I tried to do it with sticking everything into display groups and transitioning the xScale, yScale. It works visually of course but game functionality screws up. For example I have a spawner that spawns some objects, its graphics gets scaled but spawner still spawns the objects from its original position, not the new scaled position..
Do I need to keep track of every X, Y position I'm using during gameplay and transition them too? (Which is probably possible but too hard for me since I use a lot of touch events for aiming and path creating etc.) Or is there an easier way to achieve this? Please please say yes :P
I'm looking forward for your answers, thanks in advance! :)
The problem is you are scaling your image, but not your position coordinates.
You need to convert from 'original coordinates' to 'scaled coordinates'
If you scale your map size to half, you should scale the move amounts by half too.
You need to 'scale' your coordinates in the same amount your image is scaled.
For example lets assume your scale factor is 0.5, you have an image:
local scaleFactor = 0.5
image.xScale = scaleFactor
image.yScale = scaleFactor
Then you would need to scale your move amounts by this factor
thingThatIsMoved.x = thingThatIsMoved.x + (moveAmount * scaleFactor)
thingThatIsMoved.y = thingThatIsMoved.y + (moveAmount * scaleFactor)
I hope this helps, I am assuming you have a moveAmount variable and updating the position on the enterFrame() event.