I'm creating an animation function to generate imageViews randomly inside a uiView. Yet it is always returning 0,0 Please Help!
private func coinAnimation(image: UIImage) {
let imageView = UIImageView(image: image)
imageView.frame = self.view.convert(self.coinsView.frame, from: self.coinsView.superview!)
imageView.contentMode = UIView.ContentMode.scaleAspectFit
imageView.backgroundColor = .red
let frame = coinsView.frame
let x = randomInRange(lo: 0, hi: Int(frame.size.width - imageView.bounds.size.width))
let y = randomInRange(lo: 0, hi: Int(frame.size.height - imageView.bounds.size.height))
let position = CGPoint(x: x, y: y)
UIView.animate(withDuration: 1.0, delay: 0.0, options: .curveEaseInOut, animations: {
imageView.center = position
self.coinsView.addSubview(imageView)
}, completion: nil)
}
private func randomInRange(lo: Int, hi : Int) -> Int {
return lo + Int(arc4random_uniform(UInt32(hi - lo + 1)))
}
Your issue is with the line below
imageView.frame = self.view.convert(self.coinsView.frame, from: self.coinsView.superview!)
This line is setting the same frame of coinsView to imageView. And when you are trying to get random x and y points in the below code it will always return 0. because frame.size.width and imageView.bounds.size.width are same and same goes to height.
let x = randomInRange(lo: 0, hi: Int(frame.size.width - imageView.bounds.size.width))
let y = randomInRange(lo: 0, hi: Int(frame.size.height - imageView.bounds.size.height))
For better understanding add below line to your code to set imageView Frame, you need to set static frame to your image view initially.
imageView.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
Make sure width and height should be smaller than the width and height of coinsView's width & height
Related
I have a CGRect (let's call it A1) with know size and location that has a parent A of know size. A1 has all sort of CGAffineTransform applied to it.
I need to find the location and size of a second rectangle relative to a parent B of known (but different dimensions) that would look proportional to A1 in relation to A
Basically a "zoom" effect. What would A1 dimensions and position be to look similar when placed in B . Kind of a "scale the CGAffineTransform" ?
visual guide:
From:
To:
Assuming you are transforming the rect like this:
scale it
move it
rotate it
First, you want to calculate the "scaleFactor" between the two sizes. So...
if rect A was 100 x 100
and rect B was 200 x 200
The scaleFactor would be 200 / 100 == 2 ... that is, B is 2x bigger.
For your values:
rect A is 375 x 375
rect B is 1080 x 1080
the scaleFactor is 1080 / 375 == 2.88
So, let's go with values that make the math easy (and obvious):
rect A is 200 x 200
rect A1 origin is x: 30 y: 20
rect A1 size is 50 x 25
rect B is 600 x 600
scaleFactor is 600 / 200 == 3, so
rect B1 origin is x: (30 * 3) y: (20 * 3)
rect B1 size is (50 * 3) x (25 * 3)
If that's all we've done so far, we would get this result:
Next, we need to move (translate) it, and scale the translation. If the original translation is:
CGAffineTransform(translationX: 30, y: 100)
the new translation is:
CGAffineTransform(translationX: 30 * scaleFactor, y: 100 * scaleFactor)
We don't need to scale the rotation, because, well, it's the same rotation.
So, after setting the original frame based on our origin and size calculations, then applying the same scale transform, plus the scaled translation transform, plus the same rotation transform, we get:
Here's example code I used to produce that:
class ViewController: UIViewController {
let viewA: UIView = {
let v = UIView()
v.backgroundColor = .systemYellow
return v
}()
let viewARedView: UIView = {
let v = UIView()
v.backgroundColor = .systemRed
return v
}()
let viewABlueView: UIView = {
let v = UIView()
v.backgroundColor = .systemBlue
return v
}()
let viewB: UIView = {
let v = UIView()
v.backgroundColor = .systemYellow
return v
}()
let viewBRedView: UIView = {
let v = UIView()
v.backgroundColor = .systemRed
return v
}()
let viewBBlueView: UIView = {
let v = UIView()
v.backgroundColor = .systemBlue
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.setNavigationBarHidden(true, animated: false)
view.addSubview(viewA)
view.addSubview(viewB)
viewA.frame = CGRect(x: 20, y: 60, width: 200, height: 200)
viewB.frame = CGRect(x: 20, y: viewA.frame.maxY + 20, width: 600, height: 600)
viewA.addSubview(viewARedView)
viewA.addSubview(viewABlueView)
let origRect: CGRect = CGRect(x: 30, y: 20, width: 50, height: 25)
// set frames of red and blue rect to the same rectangles
// so blue is on top of red
// we're going to transform the blue rect
viewARedView.frame = origRect
viewABlueView.frame = origRect
// scale values
let scX: CGFloat = 2.25
let scY: CGFloat = 1.75
// translate values
let trX: CGFloat = 30.0
let trY: CGFloat = 100.0
// rotation value
let rot: CGFloat = .pi * -0.15
var tx: CGAffineTransform = .identity
// scale it
tx = tx.concatenating(CGAffineTransform(scaleX: scX, y: scY))
// move it
tx = tx.concatenating(CGAffineTransform(translationX: trX, y: trY))
// rotate it
tx = tx.concatenating(CGAffineTransform(rotationAngle: rot))
// apply the transform
viewABlueView.transform = tx
viewB.addSubview(viewBRedView)
viewB.addSubview(viewBBlueView)
// get the scale factor...
// in this case,
// viewA width is 200
// viewB width is 700
// so viewB is 3.5 times bigger (700 / 200)
let scaleFactor: CGFloat = viewB.frame.width / viewA.frame.width
// make the original rect origin and size
// "scaleFactor bigger"
let newRect: CGRect = CGRect(x: origRect.origin.x * scaleFactor,
y: origRect.origin.y * scaleFactor,
width: origRect.width * scaleFactor,
height: origRect.height * scaleFactor)
// set frames of red and blue rect to the same rectangles
// so blue is on top of red
// we're going to transform the blue rect
viewBRedView.frame = newRect
viewBBlueView.frame = newRect
// scale the translation values
let trXb: CGFloat = trX * scaleFactor
let trYb: CGFloat = trY * scaleFactor
var txb: CGAffineTransform = .identity
// we've already changed the initial size of the rectangle,
// so use same scaling values
txb = txb.concatenating(CGAffineTransform(scaleX: scX, y: scY))
// we need to use scaled translation values
txb = txb.concatenating(CGAffineTransform(translationX: trXb, y: trYb))
// rotation doesn't change based on scale
txb = txb.concatenating(CGAffineTransform(rotationAngle: rot))
// apply the transform
viewBBlueView.transform = txb
}
}
Let's first take a look at the Screenshot:
My problem:
I just cannot figure out how to change the spacing between the horizontal lines. This spacing is a little bit too wide, as you can see compared to the vertical lines.
This is how I implemented the pattern of UIButtons:
func buildBoard(){
for i in 0...8{
for j in 0...8{
rowBtnArr.append(setupBtn(x: i, y: j))
}
}
}
func setupBtn( x: Int, y: Int)->UIButton{
let btnSize = Int(UIScreen.main.bounds.width / 10) //40
let buffer = 1
if(y%2==0){ //even lines: normal
button = UIButton(frame: CGRect(x: x*btnSize, y: y*btnSize, width: btnSize-buffer, height: btnSize-buffer))
}else{ //odd lines: shift by 1/2 button width
button = UIButton(frame: CGRect(x: x*btnSize+(btnSize/2), y: y*btnSize, width: btnSize-buffer, height: btnSize-buffer))
}
button.layer.cornerRadius = 0.5 * button.bounds.size.width
//unimportant color and target lines are left out
return button
}
Thanks for any help!
SwiftHobby
I think one thing you have to consider is that because you are offsetting the rows every even row, there is more space vertically because the peak height of your circles matches up with the intersection of circles above it. So what I would do is subtract some number to the btnSize before you multiply it by y. This should tighten the y-spacing.
func setupBtn( x: Int, y: Int)->UIButton{
let btnSize = Int(UIScreen.main.bounds.width / 10) //40
let buffer = 1
if(y%2==0){ //even lines: normal
button = UIButton(frame: CGRect(x: x*btnSize, y: y*(btnSize-1), width: btnSize-buffer, height: btnSize-buffer))
}else{ //odd lines: shift by 1/2 button width
button = UIButton(frame: CGRect(x: x*btnSize+(btnSize/2), y: y*(btnSize-1), width: btnSize-buffer, height: btnSize-buffer))
}
button.layer.cornerRadius = 0.5 * button.bounds.size.width
//unimportant color and target lines are left out
return button
}
This way the y-spacing is closer than the x-spacing, accounting for the mismatch I was talking about earlier. You need to experiment with the y-spacing to get the visual difference you want, so my correction above might be weird looking as well.
maybe you want this effect:
I use your code and make some changes, and use a Array to cache all buttons's frame like blow:
// user a Array to cache all frames
lazy var framesMatrix: [[CGRect]] = Array(repeating: Array(repeating: CGRect.zero, count: 9), count: 9)
func setupBtn( x: Int, y: Int)->UIButton{
let btnCount = 10
let buffer: CGFloat = 1
let btnSize = (UIScreen.main.bounds.width - CGFloat(btnCount - 1) * buffer) / CGFloat(btnCount)
var button: UIButton = UIButton()
if y % 2 == 0 {
if y == 0 {
let frame = CGRect(x: CGFloat(x)*(btnSize+buffer), y: CGFloat(y)*btnSize, width: btnSize, height: btnSize)
framesMatrix[x][y] = frame
button = UIButton(frame: frame)
} else {
let lastFrame = framesMatrix[x][y-1]
// Here is the change
let newCenterY = lastFrame.midY + sqrt(3.0) * 0.5 * (btnSize+buffer);
let frame = CGRect(x: lastFrame.minX - btnSize * 0.5 , y: newCenterY - btnSize * 0.5, width: btnSize, height: btnSize);
framesMatrix[x][y] = frame
button = UIButton(frame: frame)
}
} else {
let lastFrame = framesMatrix[x][y-1]
// Here is the change
let newCenterY = lastFrame.midY + sqrt(3.0) * 0.5 * (btnSize+buffer);
let frame = CGRect(x: lastFrame.midX + buffer * 0.5, y: newCenterY - btnSize * 0.5 , width: btnSize, height: btnSize);
framesMatrix[x][y] = frame
button = UIButton(frame: frame)
}
button.layer.cornerRadius = 0.5 * button.bounds.size.width
button.backgroundColor = UIColor.black
//unimportant color and target lines are left out
return button
}
The code is added to Github to let you understand the real problem.
This is the hierarchy:
-- ViewController.View P [width: 375, height: 667]
---- UIImageView A [width: 375, height: 667] Name: imgBackground
[A is holding an image of size(1287,1662)]
---- UIImageView B [width: 100, height: 100] Name: imgForeground
[B is holding an image of size(2400,982)]
I am trying to merge A with B but the result is stretched.
This is the merge code:
func mixImagesWith(frontImage:UIImage?, backgroundImage: UIImage?, atPoint point:CGPoint, ofSize signatureSize:CGSize) -> UIImage {
let size = self.imgBackground.frame.size
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale)
backgroundImage?.draw(in: CGRect.init(x: 0, y: 0, width: size.width, height: size.height))
frontImage?.draw(in: CGRect.init(x: point.x, y: point.y, width: signatureSize.width, height: signatureSize.height))
let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return newImage
}
Note:
.contentMode = .scaleAspectFit
Code works but the result is stretched.
See this line in code, let size = self.imgBackground.frame.size – I need to change this to fix the problem. Find the origin of subview with respect to UIImage size
Here's the screenshot to understand the problem:
What should I do to get the proper output of merge function?
You have two bugs in your code:
You should also calculate aspect for document image to fit it into UIImageView. In mergeImages() replace:
img.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
with:
img.draw(in: getAspectFitFrame(sizeImgView: size, sizeImage: img.size))
When calculating aspect you center image horizontally/vertically if its width/height less then UIImageView width/height. But instead of comparing newWidth and newHeight you should compare factors:
if hfactor > vfactor {
y = (sizeImgView.height - newHeight) / 2
} else {
x = (sizeImgView.width - newWidth) / 2
}
Try bellow code it works for me, hope it works for you too,
func addWaterMarkToImage(img:UIImage, sizeWaterMark:CGRect, waterMarkImage:UIImage, completion : ((UIImage)->())?){
handler = completion
let img2:UIImage = waterMarkImage
let rect = CGRect(x: 0, y: 0, width: img.size.width, height: img.size.height)
UIGraphicsBeginImageContext(img.size)
img.draw(in: rect)
let frameAspect:CGRect = getAspectFitFrame(sizeImgView: sizeWaterMark.size, sizeImage: waterMarkImage.size)
let frameOrig:CGRect = CGRect(x: sizeWaterMark.origin.x+frameAspect.origin.x, y: sizeWaterMark.origin.y+frameAspect.origin.y, width: frameAspect.size.width, height: frameAspect.size.height)
img2.draw(in: frameOrig, blendMode: .normal, alpha: 1)
let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
if handler != nil {
handler!(result!)
}
}
//MARK - Get Aspect Fit frame of UIImage
func getAspectFitFrame(sizeImgView:CGSize, sizeImage:CGSize) -> CGRect{
let imageSize:CGSize = sizeImage
let viewSize:CGSize = sizeImgView
let hfactor : CGFloat = imageSize.width/viewSize.width
let vfactor : CGFloat = imageSize.height/viewSize.height
let factor : CGFloat = max(hfactor, vfactor)
// Divide the size by the greater of the vertical or horizontal shrinkage factor
let newWidth : CGFloat = imageSize.width / factor
let newHeight : CGFloat = imageSize.height / factor
var x:CGFloat = 0.0
var y:CGFloat = 0.0
if newWidth > newHeight{
y = (sizeImgView.height - newHeight)/2
}
if newHeight > newWidth{
x = (sizeImgView.width - newWidth)/2
}
let newRect:CGRect = CGRect(x: x, y: y, width: newWidth, height: newHeight)
return newRect
}
I am trying to make a view transform and kind of have a circle effect until it reaches certain points then it just fills a rectangle. This is for a material design project I am working on. All the code is in Swift 2.0 on an iOS 8 or above device.
func helloWorld(sender: UIButton) {
let point = sender.frame.origin
let rippleViewInitFrame: CGRect = CGRect(x: point.x, y: point.y, width: 4, height: 4)
let rippleView: UIView = UIView(frame: rippleViewInitFrame)
rippleView.backgroundColor = UIColor.MDColor.blue
let bounds: CGRect = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height)
rippleView.layer.masksToBounds = true
rippleView.layer.cornerRadius = 2
self.view.addSubview(rippleView)
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveEaseInOut, animations: {
rippleView.transform = CGAffineTransformMakeScale(200.0, 200.0)
rippleView.bounds = bounds
}, completion: {
finished in
print(rippleView.frame.origin.x)
})
}
Currently the view just grows beyond the size of the screen.
The print statement returns -37176 instead of say 0. I want it to fill the screen and nothing more.
If you want it to fill a rectangle.
1) create a rectangular container UIView.
2) add your rippleView as a subview to this rectangular uiview.
set the clipsToBounds property to yes of your container view.
and just do the animation.
let rectView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100));
rectView.backgroundColor = UIColor.redColor()
// initialRippleView
let rippleView = UIView(frame: CGRect(x: 15, y: 15, width: 2, height: 2));
rippleView.backgroundColor = UIColor.whiteColor()
rippleView.cornerRadius = rippleView.width / 2;
rectView.addSubview(rippleView);
rectView.clipsToBounds = true;
UIView.animateWithDuration(5) { () -> Void in
rippleView.transform = CGAffineTransformMakeScale(100, 100);
}
Try in playground.
import UIKit
import XCPlayground
// the main View
let iPhone = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 568));
iPhone.backgroundColor = UIColor.greenColor();
// the rect View that will be the bounds of the animation
let rectView = UIView(frame: CGRect(x: 0, y: 0, width: 150, height: 150));
rectView.backgroundColor = UIColor.redColor()
rectView.center = iPhone.center;
// initialRippleView
let rippleView = UIView(frame: CGRect(x: 15, y: 15, width: 2, height: 2));
rippleView.layer.cornerRadius = 1;
rippleView.backgroundColor = UIColor.whiteColor()
iPhone.addSubview(rectView);
rectView.addSubview(rippleView);
// this property clips the drawings of subview to be clipped to the bounds of rectView.
rectView.clipsToBounds = true;
UIView.animateWithDuration(5) { () -> Void in
// you may need to calculate the right scale factor
rippleView.transform = CGAffineTransformMakeScale(200, 200);
}
// Playground stuff
XCPShowView("Container View", view: iPhone);
Copy this code in a playground File
Show the Assistant editor
Press play (in the left bottom corner)
How can I increase the size of a CGRect by a certain percent value? Should I use some form of CGRectInset to do it?
Example:
Assume I have a CGRect: {10, 10, 110, 110}
I want to increase its size (retaining the same center point) by 20% to:
{0, 0, 120, 120}
You can use CGRectInset if you like:
double pct = 0.2;
CGRect newRect = CGRectInset(oldRect, -CGRectGetWidth(oldRect)*pct/2, -CGRectGetHeight(oldRect)*pct/2);
To decrease the size, remove the -s.
Side note: A CGRect that is 20% bigger than {10, 10, 100, 100} is {0, 0, 120, 120}.
Edit: If the intention is to increase by area, then this'll do it (even for rectangles that aren't square):
CGFloat width = CGRectGetWidth(oldRect);
CGFloat height = CGRectGetHeight(oldRect);
double pct = 1.2; // 20% increase
double newWidth = sqrt(width * width * pct);
double newHeight = sqrt(height * height * pct);
CGRect newRect = CGRectInset(oldRect, (width-newWidth)/2, (height-newHeight)/2);
In Swift:
func increaseRect(rect: CGRect, byPercentage percentage: CGFloat) -> CGRect {
let startWidth = CGRectGetWidth(rect)
let startHeight = CGRectGetHeight(rect)
let adjustmentWidth = (startWidth * percentage) / 2.0
let adjustmentHeight = (startHeight * percentage) / 2.0
return CGRectInset(rect, -adjustmentWidth, -adjustmentHeight)
}
let rect = CGRectMake(0, 0, 10, 10)
let adjusted = increaseRect(rect, byPercentage: 0.1)
// -0.5, -0.5, 11, 11
In ObjC:
- (CGRect)increaseRect:(CGRect)rect byPercentage:(CGFloat)percentage
{
CGFloat startWidth = CGRectGetWidth(rect);
CGFloat startHeight = CGRectGetHeight(rect);
CGFloat adjustmentWidth = (startWidth * percentage) / 2.0;
CGFloat adjustmentHeight = (startHeight * percentage) / 2.0;
return CGRectInset(rect, -adjustmentWidth, -adjustmentHeight);
}
CGRect rect = CGRectMake(0,0,10,10);
CGRect adjusted = [self increaseRect:rect byPercentage:0.1];
// -0.5, -0.5, 11, 11
Sure, using CGRectInset works:
CGRect someRect = CGRectMake(10, 10, 100, 100);
someRect = CGRectInset(someRect, someRect.size.width * -0.2, someRect.size.height * -0.2);
Swift 4 extension inspired by several of the answers here with simplified calculations:
extension CGRect {
func scaleLinear(amount: Double) -> CGRect {
guard amount != 1.0, amount > 0.0 else { return self }
let ratio = ((1.0 - amount) / 2.0).cgFloat
return insetBy(dx: width * ratio, dy: height * ratio)
}
func scaleArea(amount: Double) -> CGRect {
return scaleLinear(percent: sqrt(amount))
}
func scaleLinear(percent: Double) -> CGRect {
return scaleLinear(amount: percent / 100)
}
func scaleArea(percent: Double) -> CGRect {
return scaleArea(amount: percent / 100)
}
}
Usage is simply:
rect.scaleLinear(percent: 120.0) OR (amount: 1.2)
rect.scaleArea(percent: 120.0) OR (amount: 1.2)
If you are interested in trying my testing methods:
/// Testing
extension CGRect {
var area: CGFloat { return width * height }
var center: CGPoint { return CGPoint(x: origin.x + width/2, y: origin.y + height/2)
}
func compare(_ r: CGRect) {
let centered = center.x == r.center.x && center.y == r.center.y
print("linear = \(r.width / width), area = \(r.area / area) centered \(centered)")
}
static func ScaleTest() {
let rect = CGRect(x: 17, y: 24, width: 200, height: 100)
let percent = 122.6
rect.compare(rect.scaleLinear(percent: percent))
rect.compare(rect.scaleArea(percent: percent))
}
}
I'm using CGRect > insetBy in my Swift code
https://developer.apple.com/documentation/coregraphics/cgrect/1454218-insetby
With this, your percent value will be the scaleX as my example.
let dx = rectWidth*scaleX
let dy = rectHeight*scaleX
let rectangle = CGRect(x: rectX,
y: rectY,
width: rectWidth,
height: rectHeight).insetBy(dx: -dx, dy: -dy)
use positive value to scale down
use negative value to scale up
Using Swift you can retain the center point (relative to the source Rect) and increase/decrease the size as follows using an extension:
extension CGRect {
func centerAndAdjustPercentage(percentage p: CGFloat) -> CGRect {
let x = self.origin.x
let y = self.origin.y
let w = self.width
let h = self.height
let newW = w * p
let newH = h * p
let newX = (w - newW) / 2
let newY = (h - newH) / 2
return CGRect(x: newX, y: newY, width: newW, height: newH)
}
}
let newRect = oldRect.centerAndAdjustPercentage(percentage: 0.25)
let scale = percent / 100
let newRect = oldRect.applying(CGAffineTransform(scaleX: scale, y: scale))
Beware that this approach also will change x and y of rectangle.