Above picture illustrates what I want:
The red circle is the starting point of a UIView
I want to launch it upwards (Y position) with a change in the X position
I want to make it drop within boundaries.
What I tried:
For the launch I can make use of the UIView.animate function. For the drop I can use the UIDynamicAnimator. However there are some problems:
-In the UIView.animate I cannot 'curve' the animation, only a straight line. I can use this answer here to draw a curve line: https://stackoverflow.com/a/43813458/7715250
-Combining both functions is not working. After the UIView.animate is done, the UIView just drop straight downwards.
The code for UIDynamicAnimator:
var animator: UIDynamicAnimator!
//view did appear
let view2 = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view2.center = self.view.center
view2.backgroundColor = .blue
self.view.addSubview(view2)
animator = UIDynamicAnimator(referenceView: self.view)
let gravity = UIGravityBehavior(items: [view2])
animator.addBehavior(gravity)
let collosion = UICollisionBehavior(items: [view2])
collosion.translatesReferenceBoundsIntoBoundary = true
let dynamic = UIDynamicItemBehavior(items: [view2])
dynamic.elasticity = 0.7
animator.addBehavior(collosion)
animator.addBehavior(dynamic)
That will drop the UIView with a nice bounce effect. But how to launch the UIView? How to change the X and Y position and remain the added behaviours?
I think what you need is UIPushBehavior. Add this extra behavior.
let push = UIPushBehavior(items: [view2], mode: UIPushBehaviorMode.instantaneous)
push.setAngle(CGFloat(-M_PI/2 - 0.1), magnitude: 8.0) // Adjust angle and magnitude
animator.addBehavior(push)
Related
This question already has an answer here:
How to align UIButtons within a circle?
(1 answer)
Closed 4 years ago.
I am attempting to plot circular buttons in a ring like this.
I have 6 UI buttons which all have a corner-radius of 360 degrees, what I am attempting to do in Swift is plot them equally around a central button/point just like the image above. Each circle is equally spaced from its neighbour. What is the best way of achieving this functionality? Should I create a bezier curve and then use math to plot the buttons around said curve or is there another way which you would suggest?
A side note is that I am not looking for a radial menu of any kind there is no animation required. I am just trying to plot my existing UI Buttons in the format above.
Thanks!
I would suggest using math (trigonometry) to compute the horizontal and vertical offsets from the center button and then use layout anchors to position the buttons.
Here is a self contained example:
class ViewController: UIViewController {
func createButton(size: CGFloat) -> UIButton {
let button = UIButton(type: .custom)
button.backgroundColor = .red
button.translatesAutoresizingMaskIntoConstraints = false
button.widthAnchor.constraint(equalToConstant: size).isActive = true
button.heightAnchor.constraint(equalToConstant: size).isActive = true
button.layer.cornerRadius = size / 2
return button
}
func setUpButtons(count: Int, around center: UIView, radius: CGFloat) {
// compute angular separation of each button
let degrees = 360 / CGFloat(count)
for i in 0 ..< count {
let button = createButton(size: 50)
self.view.addSubview(button)
// use trig to compute offsets from center button
let hOffset = radius * cos(CGFloat(i) * degrees * .pi / 180)
let vOffset = radius * sin(CGFloat(i) * degrees * .pi / 180)
// set new button's center relative to the center button's
// center using centerX and centerY anchors and offsets
button.centerXAnchor.constraint(equalTo: center.centerXAnchor, constant: hOffset).isActive = true
button.centerYAnchor.constraint(equalTo: center.centerYAnchor, constant: vOffset).isActive = true
}
}
override func viewDidLoad() {
super.viewDidLoad()
let centerButton = createButton(size: 50)
self.view.addSubview(centerButton)
// use anchors to place center button in the center of the screen
centerButton.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
centerButton.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
setUpButtons(count: 6, around: centerButton, radius: 100)
}
}
Notes:
If you don't need the center button, just set up the buttons around self.view:
setupButtons(count: 6, around: self.view, radius: 100)
or around an arbitrary point:
let point = CGPoint(x: 180, y: 300)
let centerView = UIView(frame: CGRect(origin: point, size: CGSize.zero))
self.view.addSubview(centerView)
setUpButtons(count: 6, around: centerView, radius: 140)
Using a UIView as the center instead of a point is more flexible because you can dynamically move that UIView and the buttons will follow.
Here it is running in the simulator:
You don't need a bezierpath for that, just a bit of calculation.
I am not a math genius or something so I took an already answered question and changed it to my needs.
func place(buttons: [UIView], around center: CGPoint, radius: CGFloat) {
var currentAngle: CGFloat = 0
let buttonAngle: CGFloat = (CGFloat(360 / buttons.count)) * .pi / 180
for button in buttons {
let buttonCenter = CGPoint(x: center.x + cos(currentAngle) * radius, y: center.y + sin(currentAngle) * radius)
// (1)
//button.transform = CGAffineTransform(rotationAngle: currentAngle)
// (2)
//view.addSubview(button)
button.center = buttonCenter
currentAngle += buttonAngle
}
}
Use an array of UIView so you are flexible. If you want to add the buttons from code, just uncomment (1). If you want the buttons to rotate according to their rotation around the center, uncomment (2).
Btw it is not necessary to add 360 degree cornerradius to the buttons for a round shape. Please have a read on the documentation here.
Happy coding!
I want to make my game full screen not with the black bars on both sides and bottom. so I found this code for that and it works for scene and now it's on full screen but the assets are not correctly positioned. Because of the view's coordinates are different (don't know why maybe because of previously I tried to set all for safeAreaLayoutGuide but then I get black bars). So is there any way to set it back to 0.5 0.5 . Otherwise I will have to set back anchorPoint of scene to what ever is view's anchorPoint and set all assets in my sks file according to that.
Here is the code from GameViewController :
class GameViewController: UIViewController {
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if let view = self.view as! SKView? {
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
let scene = HomeScene(size: view.bounds.size)
scene.scaleMode = .aspectFill
view.presentScene(scene)
}
}
override func viewDidLoad() {
super.viewDidLoad()
print("---")
print("∙ \(type(of: self))")
print("---")
}
}
this code from HomeScene :
let rectangle1 = SKShapeNode(rect: (self.view?.bounds)!)
rectangle1.strokeColor = .white
rectangle1.lineWidth = 20
addChild(rectangle1)
// Set (0, 0) as the centre of the screen
scene?.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let rectangle = SKShapeNode(rect: self.frame)
rectangle.strokeColor = .green
rectangle.lineWidth = 20
addChild(rectangle)
This is how it looks the scene is at 0.5, 0.5 but, view is above that green is scene and white is view and button in middle of the view :
Thank you.
You could fix the problem with a different anchorpoint, or you could position the white 'view' node differently in the scene.
Information about the coordinate system (and anchor points): apple documentation
The default anchor position is (0.5, 0.5) according to the documentation. To put the white view in the bottom left: subtract half of the width and height of the x and y. Note that the line width also affects the positioning. Position the white view to the bottom left:
let bottomLeft: CGPoint = CGPoint(x: -self.size.width/2 + rectangle1.lineWidth/2,
y: -self.size.height/2 + rectangle1.lineWidth/2)
rectangle1.position = bottomLeft
To center the white view in the green view, subtract the width and height of rectangle1 (again, note the line width)
let center: CGPoint = CGPoint(x: -rectangle1.frame.size.width/2 + rectangle1.lineWidth/2,
y: -rectangle1.frame.size.height/2 + rectangle1.lineWidth/2)
rectangle1.position = center
Note that your green view is above the white view. Set the z position of the white view to some positive number to arrange it above the green view
rectangle1.zPosition = 1
If you were loading your HomeScene from a .sks file, the anchorPoint was set by xcode to (0.5, 0.5) as shown in my image.
But when you are trying to create a custom class without loading it from file (.sks), you will not have the properties set like here. So you need to set the manually.
let scene = HomeScene(size: view.bounds.size)
// at this point, you HomeScene has the anchorPoint set to (0.0, 0.0)
// lets set it back to (0.5, 0.5) as we want
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
scene.scaleMode = .aspectFill
view.presentScene(scene)
I have a UIButton and I wanna add a circle shape in the middle of it.
how can I do that?
Here's what I want to achieve.
Thanks/
Insert a subview in your button
let circleFrame = CGRect(x: 0, y: 0, width: circleDimension, height: circleDimension)
let circle = UIView(frame: circleFrame)
circle.backgroundColor = UIColor.clear
circle.layer.borderWidth = circleWidth
circle.layer.borderColor = UIColor.white.cgColor
circle.layer.cornerRadius = circleDimension/2
Just add that as a subview to your button and set centerX and centerY anchors equal to your button. Then, if you want to make it square, just do
circle.layer.cornerRadius = 0
There are many ways to do this including using CoreGraphics. The easiest way is to just set an image on the button
How to set image of UIButton in Swift 3?
If you need it clickable ofcourse, otherwise just use a UIImageView(which you can also make clickable using UITapGestureRecognizer)
I'm using ios 9 sdk. I created this view hierarchy setup with autoloayout:
----- Top
| 30
----- CustomView (A)
|
|
-----
| 0
----- Bot
I'm trying to create a second CustomView (call it B) behind the first (call it A), scaled by 80% in both directions, and above A by 5 points. So only the top of B is visible.
This should be easy, but it is not. Applying a -5 points translation and a .8 scaling on B moves B down instead of up, because autolayout seems to use the anchor point (set to middle by default) to position B: it seems it detects that a scaling has been applied and recenters B vertically - moving it down.
Changing the Anchor point to (x=0.5,y=1) moves A and B up by half their size, i don't understand why. So it does not fix the problem.
Any idea ?
Edit: some code
var card = new CardView();
InsertSubview(card, 0);
card.TranslatesAutoresizingMaskIntoConstraints = false;
this.AddConstraints(
card.AtTopOf(this, 30),
card.WithSameWidth(this).WithMultiplier(.95f),
card.WithSameCenterX(this),
card.AtBottomOf(this)
);
var transform = CGAffineTransform.MakeTranslation(0, -10);
transform.Scale(.8f,.8f);
Transform = transform;
Result: card is not at 20pts from top, it is at about 50pts, beside its original position of 30pt.
You're adding the wrong constraints. After some experimentation I got the results you want. The key seems to be to pin the center Y of the background view to the top of the foreground view, this then offsets the vertical shift given when changing the anchor point.
The following code in a playground gives the results you want:
import UIKit
import XCPlayground
let view = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 400, height:400)))
view.backgroundColor = UIColor.whiteColor()
XCPlaygroundPage.currentPage.liveView = view
let a = UIView()
a.translatesAutoresizingMaskIntoConstraints = false
a.backgroundColor = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.5)
view.addSubview(a)
a.topAnchor.constraintEqualToAnchor(view.topAnchor, constant: 30).active = true
a.widthAnchor.constraintEqualToAnchor(view.widthAnchor).active = true
a.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor).active = true
let b = UIView()
b.translatesAutoresizingMaskIntoConstraints = false
b.backgroundColor = UIColor(red: 0, green: 1, blue: 0, alpha: 1)
b.layer.anchorPoint = CGPoint(x: 0.5, y: 0)
view.addSubview(b)
view.sendSubviewToBack(b)
NSLayoutConstraint.activateConstraints([
b.centerYAnchor.constraintEqualToAnchor(a.topAnchor),
b.widthAnchor.constraintEqualToAnchor(a.widthAnchor),
b.heightAnchor.constraintEqualToAnchor(a.heightAnchor),
b.centerXAnchor.constraintEqualToAnchor(a.centerXAnchor)
])
let translate = CGAffineTransformMakeTranslation(0.0, -10)
let scale = CGAffineTransformMakeScale(0.8, 0.8)
b.transform = CGAffineTransformConcat(translate, scale)
Results:
The two possible approaches I see are:
- scale first, then add it to the view hierarchy
or
- change the anchorpoint of view B before you add it to the view hierarchy. Then you can transform it the way you want.
I've made a simple one-screen app, that has a custom LadderView on the left side of the screen, TableView on the right side and a sliding ChartView, that lies underneath the LadderView and can be dragged to be shown over the TableView.
The setup code in the ChartView is something like this:
func setup () {
let screenHeight = self.superview!.frame.size.height
let screenWidth = self.superview!.frame.size.width
self.panGestureReconizer = UIPanGestureRecognizer(target: self, action: #selector(GraphCentreView.handlePan(_:)))
panGestureReconizer.cancelsTouchesInView = false
self.addGestureRecognizer(panGestureReconizer)
self.animator = UIDynamicAnimator(referenceView: self.superview!)
self.dynamicItem = UIDynamicItemBehavior(items: [self])
self.dynamicItem.allowsRotation = false
self.dynamicItem.elasticity = 0
snap = UISnapBehavior(item: self, snapToPoint: CGPoint(x: -screenWidth + 80, y: screenHeight / 2))
animator.addBehavior(snap)
self.gravity = UIGravityBehavior(items: [self])
self.gravity.gravityDirection = CGVectorMake(-5, 0)
self.container = UICollisionBehavior(items: [self])
self.container.addBoundaryWithIdentifier("leftmost", fromPoint: CGPointMake(-self.frame.size.width + 80, 0), toPoint: CGPointMake(-self.frame.size.width + 80, screenHeight))
container.addBoundaryWithIdentifier("rightmost", fromPoint: CGPointMake(screenWidth, 0), toPoint: CGPointMake(screenWidth, screenHeight))
animator.addBehavior(gravity)
animator.addBehavior(dynamicItem)
animator.addBehavior(container)
}
The constraints for the ChartView are set to snap to both top and bottom layout guides of the superView. Problem is, though, that the ChartView is shifted a little bit up. I've gone through my code a thousand times, but I can't find anything that would mess with the y coordinate of the GraphView. When I don't add the behaviors to the animator, however, the view is placed just fine, according to the constraints. Could you help me find the problem?
You can see the space between the TabBar and the blurred sliding GraphView.