I'm trying to get the center of my view by using the code below, the class being used is from the DTIActivityIndicatorView which add a custom activity indicator to the view.
var midX = CGRectGetMidX(self.view.bounds)
var midY = CGRectGetMidY(self.view.bounds)
let topStoriesActivityIndicator: DTIActivityIndicatorView = DTIActivityIndicatorView(frame: CGRect(x: midX, y: midY, width:80.0, height:80.0))
But the code above is giving me the following result where the activity indicator isn't centered in the middle.
Swift 4 extension:
extension CGRect {
var center: CGPoint { .init(x: midX, y: midY) }
}
let someRect = CGRect(x: 10, y: 10, width: 200, height: 40)
let theCenter = someRect.center // {x 110 y 30}
Swift 3
The following code represent the center of Screen:
let frameSize: CGPoint = CGPoint(x: UIScreen.main.bounds.size.width*0.5,y: UIScreen.main.bounds.size.height*0.5)
Hope this help!
Bounds can be different than how size of the view appears. Try using frame and if it doesn't work try using bounds.
var midX = CGRectGetMidX(self.view.bounds)
var midY = CGRectGetMidY(self.view.bounds)
Also since you are positioning your view in the center and then adding width and height, it appears off as well. You should set the frame like this
let topStoriesActivityIndicator: DTIActivityIndicatorView = DTIActivityIndicatorView(frame: CGRect(x: midX - 80.0, y: midY - 80.0, width:80.0, height:80.0))
Swift 3:
let someRect: CGRect(x: 0, y: 0, width: 10, height: 10)
let midX = someRect.midX
let midY = someRect.midY
Try this :-
var midY = self.view.frame.height / 2
var midX = self.view.frame.width / 2
Swift 5
let frameSize: CGPoint = CGPoint(x:self.view.bounds.midX, y:self.view.bounds.midY)
let frameSize: CGPoint = CGPoint(x:yourView.bounds.midX, y:yourView.bounds.midY)
Or
let frameSize: CGPoint = self.view.center
How about this?
let width = 80
let height = 80
let
topStoriesActivityIndicator: DTIActivityIndicatorView = DTIActivityIndicatorView(frame: CGRect(x: view.bounds.midX - ( width * 0.5), y: view.bounds.midY - ( height * 0.5) , width: width, height: height))
This may be able to help resolve the problem
Related
I'm making a loading spinner animation that pushes a view out from the middle, and then rotates all the way around the center view back to it's original location. This is what I am trying to achieve:
The inner arrow moves the view away from the center. I've already achieved this, the part I am stuck on is then rotating the view around the center view. I've read various other StackOverflow posts but have not been close to achieving this.
Code so far:
UIView.animate(withDuration: 0.5) {
self.topView.transform = CGAffineTransform(translationX: 20, y: -20)
} completion: { _ in
self.topView.setAnchorPoint(self.centerView.center)
// Rotate
}
}
Here is how I am setting the anchor point of the view. I'm using this as the view disappears when setting its anchor point otherwise.
func setAnchorPoint(_ point: CGPoint) {
var newPoint = CGPoint(x: bounds.size.width * point.x, y: bounds.size.height * point.y)
var oldPoint = CGPoint(x: bounds.size.width * layer.anchorPoint.x, y: bounds.size.height * layer.anchorPoint.y);
newPoint = newPoint.applying(transform)
oldPoint = oldPoint.applying(transform)
var position = layer.position
position.x -= oldPoint.x
position.x += newPoint.x
position.y -= oldPoint.y
position.y += newPoint.y
layer.position = position
layer.anchorPoint = point
}
Once the full 360 rotation is complete I would then need to move the view back in towards the center, completing the animation.
For the part when the loading view rotates around the circle view, you can use a UIBezierPath and create a CAKeyframeAnimation based on its path.
Take a look at this implementation. Hope it helps.
class LoadingViewController: UIViewController {
var circlePath: UIBezierPath!
lazy var loader = makeLoader()
lazy var centerView = makeCenterView()
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
func makeLoader() -> UIButton {
let padding: CGFloat = 100
let width = self.view.frame.width - (2 * padding)
let b = UIButton(frame: CGRect(x: padding, y: 200, width: width, height: 50))
b.addTarget(self, action: #selector(didTap), for: .touchUpInside)
b.backgroundColor = .blue
return b
}
func makeCenterView() -> UIView {
let width: CGFloat = 20
let height: CGFloat = 20
let x = self.view.center.x - width/2
let y = self.view.center.y - height/2
let view = UIView(frame: CGRect(x: x, y: y, width: width, height: height))
view.backgroundColor = .green
return view
}
func setup() {
//create a UIBezierPath with a center that is at the center of the green view and a radius that has a length as the distance between the green view and the blue view.
let arcCenterX = centerView.center.x
let arcCenterY = centerView.center.y
let arcCenter = CGPoint(x: arcCenterX, y: arcCenterY)
let radius = arcCenterY - loader.center.y
let startAngle = -CGFloat.pi/2
let endAngle = CGFloat.pi*(1.5)
let arcPath = UIBezierPath(arcCenter: arcCenter, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
self.circlePath = arcPath
self.view.addSubview(loader)
self.view.addSubview(centerView)
}
#objc func didTap() {
//create a CAKeyframeAnimation with a path that matches the UIBezierPath above.
let loadAnimation = CAKeyframeAnimation(keyPath: "position")
loadAnimation.path = self.circlePath.cgPath
loadAnimation.calculationMode = .paced
loadAnimation.duration = 2.0
loadAnimation.rotationMode = .rotateAuto
loadAnimation.repeatCount = Float(CGFloat.greatestFiniteMagnitude)
loader.layer.add(loadAnimation, forKey: "circleAnimation")
}
}
How can i draw custom oval shape like in below image in swift (not swiftUI).
Thank you in advance
I have tried to clone similar view using UIView and was able to create similar UI as you have stated on screenshot
And here is my code snippet
let width: CGFloat = view.frame.size.width
let height: CGFloat = view.frame.size.height
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: self.view.bounds.size.height), cornerRadius: 0)
let rect = CGRect(x: width / 2 - 150, y: height / 2.5 - 100, width: 300, height: 400)
let circlePath = UIBezierPath(ovalIn: rect)
path.append(circlePath)
path.usesEvenOddFillRule = true
let fillLayer = CAShapeLayer()
fillLayer.path = path.cgPath
fillLayer.fillRule = .evenOdd
fillLayer.fillColor = UIColor.red.cgColor
fillLayer.opacity = 0.5
view.layer.addSublayer(fillLayer)
Hope this helps you. Good day.
You can draw oval path like this
class CustomOval: UView {
override func drawRect(rect: CGRect) {
var ovalPath = UIBezierPath(ovalIn: rect)
UIColor.gray.setFill()
ovalPath.fill()
}
}
I'm trying to make a border like this:
With this code:
self.slot1.layer.cornerRadius = self.slot1.bounds.height / 2
self.slot1.layer.borderWidth = 1.5
self.slot1.layer.borderColor = UIColor.orange.cgColor
It produces:
How can i add a "spacing" between the border and the actual image?
You can achieve this using below extension
extension UIImage {
func imageWithInsets(insets: UIEdgeInsets) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(
CGSize(width: self.size.width + insets.left + insets.right,
height: self.size.height + insets.top + insets.bottom), false, self.scale)
let _ = UIGraphicsGetCurrentContext()
let origin = CGPoint(x: insets.left, y: insets.top)
self.draw(at: origin)
let imageWithInsets = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return imageWithInsets
}
}
Use below code to add space between image and border, i add here 20 you can give your own space
self.slot1.layer.cornerRadius = self.slot1.bounds.height / 2
self.slot1.layer.borderWidth = 1.5
self.slot1.layer.borderColor = UIColor.orange.cgColor
let image = self.slot1.image?.imageWithInsets(insets: UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20))
self.slot1.image = image
Hope this help!
Instead of trying to make space in between, take one UIView inside that put your UIImageView. UIImageView should have less width and height, then add corner radius to both but add border color to UIVIew only.
Your UI should be like this :
Run in playground
import PlaygroundSupport
import UIKit
let iv = UIImageView(frame: CGRect(x: 0.0, y: 0.0, width: 200, height: 200))
PlaygroundPage.current.liveView = iv
iv.backgroundColor = .blue
iv.layer.cornerRadius = 100
iv.layer.borderColor = UIColor.red.cgColor
iv.layer.borderWidth = 5
iv.layer.sublayers?.count
let layer2 = CAShapeLayer()
let newFrame = iv.bounds.insetBy(dx: 7, dy: 7)
layer2.path = UIBezierPath(roundedRect: newFrame, cornerRadius: newFrame.height / 2).cgPath
layer2.frame = iv.bounds
layer2.lineWidth = 5
layer2.strokeColor = UIColor.yellow.cgColor
layer2.fillColor = UIColor.clear.cgColor
iv.layer.addSublayer(layer2)
I have a transformed UIImage inside a UIVIew with clipsToBounds = true and I want to recreate (redraw) it exactly using a CGContext. It's hard to explain, here is the code:
class ViewController: UIViewController {
#IBOutlet weak var topView: UIView!
#IBOutlet weak var topImageView: UIImageView!
#IBOutlet weak var bottomImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// the example image
let image = UIImage(named: "image")!
// topImageView is embedded inside topView so that the image is being clipped
topView.clipsToBounds = true
topImageView.image = image
topImageView.contentMode = .center // I set this to center because it seems to be easier to draw it then
// The transformation data
let size = CGSize(width: 800, height: 200)
let scale: CGFloat = 1.5
let offset: (x: CGFloat, y: CGFloat) = (x: 0.0, y: 0.0)
// transform the imageView
topImageView.transform = CGAffineTransform(translationX: offset.x, y: offset.y).scaledBy(x: scale, y: scale)
// Now try to recreate the transform by using a transformed CGContext
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale)
let context = UIGraphicsGetCurrentContext()!
// transform the context
// Almost the same as above, but I additionally move the context so that the middle of the image is in the middle of the size defined above
context.concatenate(CGAffineTransform(translationX: (-image.size.width / 2 + size.width / 2) + offset.x, y: (-image.size.height / 2 + size.height / 2) + offset.y).scaledBy(x: scale, y: scale))
image.draw(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))
bottomImageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
}
This is working only when I have scale set to 1.0 (so no scale). Otherwise, I don't get the correct image. Here's a screenshot so that you see it:
It's not the same. It's hard to figure out how to correctly transform the context, I don't entirely understand it. Any ideas?
Thanks!
It's a mystery. I've literally spent hours trying to get this right. 5 minutes after posting this question, I solved it. It's actually not that hard if you draw it (I used photoshop to visualize the coordinate system and played around a little bit. Should have done this earlier..)
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale)
let context = UIGraphicsGetCurrentContext()!
context.concatenate(CGAffineTransform.identity.scaledBy(x: scale, y: scale).translatedBy(x: size.width / (2 * scale), y: size.height / (2 * scale)).translatedBy(x: offset.x / scale, y: offset.y / scale))
image.draw(in: CGRect(x: -image.size.width / 2, y: -image.size.height / 2, width: image.size.width, height: image.size.height))
bottomImageView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
I am trying to display an image as the content of a CALayer slightly zoomed in by changing its bounds to a bigger size. (This is so that I can pan over it later.)
For some reason however setting the bounds does not change them or trigger an animation to do so.
This is the code I use to change the bounds:
self.imageLayer.bounds = CGRect(x: 0, y: 0, width: 10, height: 10)
I have a function to compute the CGRect, but this dummy one leads to exactly the same result of the size not changing.
I have also determined, that while I can't see the size change, if I check the bounds of the layer right after setting it, it correctly has the value I set it to.
The following code is executed after setting the bounds. I couldn't find anything in it, that changes them back.
self.imageLayer.add(self.generatePanAnimation(), forKey: "pan")
func generatePanAnimation() -> CAAnimation {
var positionA = CGPoint(x: (self.bounds.width / 2), y: self.bounds.height / 2)
var positionB = CGPoint(x: (self.bounds.width / 2), y: self.bounds.height / 2)
positionA = self.generateZoomedPosition()
positionB = self.generateZoomedPosition()
let panAnimation = CABasicAnimation(keyPath: "position")
if self.direction == .AtoB {
panAnimation.fromValue = positionA
panAnimation.toValue = positionB
} else {
panAnimation.fromValue = positionB
panAnimation.toValue = positionA
}
panAnimation.duration = self.panAndZoomDuration
self.panAnimation = panAnimation
return panAnimation
}
func generateZoomedPosition() -> CGPoint {
let maxRight = self.zoomedImageLayerBounds.width / 2
let maxLeft = self.bounds.width - (self.zoomedImageLayerBounds.height / 2)
let maxUp = self.zoomedImageLayerBounds.height / 2
let maxDown = self.bounds.height - (self.zoomedImageLayerBounds.height / 2)
let horizontalFactor = CGFloat(arc4random()) / CGFloat(UINT32_MAX)
let verticalFactor = CGFloat(arc4random()) / CGFloat(UINT32_MAX)
let randomX = maxLeft + horizontalFactor * (maxRight - maxLeft)
let randomY = maxDown + verticalFactor * (maxUp - maxDown)
return CGPoint(x: randomX, y: randomY)
}
I even tried setting the bounds as shown below, but it didn't help.
CATransaction.begin()
CATransaction.setValue(true, forKey: kCATransactionDisableActions)
self.imageLayer.bounds = CGRect(x: 0, y: 0, width: 10, height: 10)
CATransaction.commit()
I really hope someone has an idea. Thanks a lot!
The way to change the apparent drawing size of a layer is not to change its bounds but to change its transform. To make the layer look larger, including its drawing, apply a scale transform.