Update position of UIImageView Swift - uiview

I'm trying to make a draggable image (monkey) takes the place of another draggable image (banana) when I'm pushing a button.
So far, I've been able to get the coordinates of the banana, and store them as the new x,y coordinates of the monkey. But, the view is not updating and the monkey does not moves to it's new place.
I'm sure that I need to update the view, but can't figure it out.
I've tried to add a new subview (self.view.addSubview(monkey)) but when I do that, it's the entire view that is reset and both monkey and banana are going back to their original coordinates.
Here's the code of the action button :
#IBAction func getCoordinates (sender : AnyObject) {
var bananax = banana.frame.origin.x
var bananay = banana.frame.origin.y
var monkeyx = monkey.frame.origin.x
var monkeyy = monkey.frame.origin.y
println("Banana : \(bananax) et \(bananay)")
println("Monkey's default coordinates : \(monkeyx) et \(monkeyy)")
monkeyx = bananax
monkeyy = bananay
println("Monkey's new coordinates : \(monkeyx) et \(monkeyy)")
}
Any idea on how to update only the monkey and not reseting the entire view ?
Thanks :)

These values are just thrown away when you set monkeyx and monkeyy below.
var monkeyx = monkey.frame.origin.x
var monkeyy = monkey.frame.origin.y
Assigning the values below just assigns the values from one local variable to another. It does not actually change the coordinates for monkey.
monkeyx = bananax
monkeyy = bananay
You must set the new coordinates on monkey by constructing a new CGRect and assigning monkey's frame:
monkey.frame = CGRect(x: bananax, y: bananay, width: monkey.frame.size.width, height: monkey.frame.size.height)
As you are just setting monkey's origin to banana's origin, you can remove all the var declarations and reduce this to one line by constructing monkey's new frame with monkey's size and banana's origin:
monkey.frame = CGRect(origin: banana.frame.origin, size: monkey.frame.size)

Hi create this extends if you want. For Swift
Create File Extends.Swift and add this code
/**
Extension UIView
by DaRk-_-D0G
*/
extension UIView {
/**
Set x Position
:param: x CGFloat
by DaRk-_-D0G
*/
func setX(#x:CGFloat) {
var frame:CGRect = self.frame
frame.origin.x = x
self.frame = frame
}
/**
Set y Position
:param: y CGFloat
by DaRk-_-D0G
*/
func setY(#y:CGFloat) {
var frame:CGRect = self.frame
frame.origin.y = y
self.frame = frame
}
/**
Set Width
:param: width CGFloat
by DaRk-_-D0G
*/
func setWidth(#width:CGFloat) {
var frame:CGRect = self.frame
frame.size.width = width
self.frame = frame
}
/**
Set Height
:param: height CGFloat
by DaRk-_-D0G
*/
func setHeight(#height:CGFloat) {
var frame:CGRect = self.frame
frame.size.height = height
self.frame = frame
}
}
For Use (inherits Of UIView)
inheritsOfUIView.setX(x: 100)
button.setX(x: 100)
view.setY(y: 100)

Now you can do
monkey.frame.origin = banana.frame.origin

If you are using a UIImageView for monkey and banana, you can simplify this by doing the following
monkey.center = banana.center
Understand that there is a difference between origin and center. See more here:
UIView's frame, bounds, center, origin, when to use what?
but based on question, I think center should work fine and it is simpler.

Related

circle collectionview image according to device size

Hello in my horizontal collectionview i want to circle image , if i set static height and width it works but it does not work if i set constraint
iam using this method to circle my image
public static func circularImageWhite(photoImageView: UIImageView?)
{
photoImageView!.layer.frame = photoImageView!.layer.frame.insetBy(dx: 0, dy: 0)
photoImageView!.layer.borderColor = UIColor.white.cgColor
photoImageView!.layer.cornerRadius = photoImageView!.frame.height/2
photoImageView!.layer.masksToBounds = false
photoImageView!.clipsToBounds = true
photoImageView!.layer.borderWidth = 1
photoImageView!.contentMode = UIViewContentMode.scaleAspectFill
}
i want to circle image on every device
Everything about your code is wrong.
photoImageView!.layer.frame = photoImageView!.layer.frame.insetBy(dx: 0, dy: 0)
That line is meaningless. If you inset the frame by zero you are not changing it. So that line does nothing at all.
photoImageView!.layer.masksToBounds = false
photoImageView!.clipsToBounds = true
A layer's masksToBounds and a view's clipsToBounds are actually the very same property. So you are setting the same property to false and then back to true in the very next line. Thus the first of those two lines does nothing at all.
photoImageView!.layer.cornerRadius = photoImageView!.frame.height/2
That is actually the heart of the matter. The problem is that you are setting the corner radius according to the frame height. But that assumes you know what the frame is. You don't. As you yourself said, this doesn't work if you set autolayout constraints on your view. Why? Because of the order in which things happen:
First, you use the current frame height to set the corner radius.
Then, the constraints kick in and change the frame. So now the corner radius that you set before doesn't "fit" the image view any more.
Moreover, setting the corner radius is a lousy way to clip a view to a circle. The correct way is to mask the view to an actual circle.
So, to sum up: You should use a UIImageView subclass that overrides its own layoutSubviews to set its own mask to a circle that fits the current size. As the size changes due to constraints, layoutSubviews will be called and your code will change the mask to fit properly.
(The white circular border can be yet another layer or subview that draws the circle.)
The matter comes up quite often, and I often see the cornerRadius misused in this same way, so here's an actual implementation:
class CircleImageView : UIImageView {
override func layoutSubviews() {
super.layoutSubviews()
self.layer.sublayers = nil
let radius = min(self.bounds.height, self.bounds.width)/2
let cen = CGPoint(x:self.bounds.width/2, y:self.bounds.height/2)
let r = UIGraphicsImageRenderer(size:self.bounds.size)
var im : UIImage?
var outline : UIImage?
r.image { ctx in
let con = ctx.cgContext
UIColor.black.setFill()
con.addArc(center: cen, radius: radius,
startAngle: 0, endAngle: .pi*2, clockwise: true)
let p = con.path
con.fillPath()
im = ctx.currentImage
con.clear(CGRect(origin:.zero, size:self.bounds.size))
con.addPath(p!)
UIColor.clear.setFill()
UIColor.white.setStroke() // border color, change as desired
con.setLineWidth(4) // border width, change as desired
con.strokePath()
outline = ctx.currentImage
}
// the circle mask
let iv = UIImageView(image:im)
iv.contentMode = .center
iv.frame = self.bounds
self.mask = iv
// the border
let iv2 = UIImageView(image:outline)
iv2.contentMode = .center
iv2.frame = self.bounds
self.addSubview(iv2)
}
}
Result:
Use CircleImageView as your image view and you'll get the right result. I repeat: the important thing is that this will continue to work no matter how the CircleImageView itself is subsequently resized.

Swift: Changing UIView frame but display doesn't update

I'm trying to dynamically resize the frame of a UIView. I can update the frame in the code and see that the values have changed, but the actual display never changes, even though I run setNeedsDisplay().
I'm following this post, where I define a new frame via CGRectMake, and then set the UIView's frame to this new frame. i.e., I'm doing this...
let newFrame = CGRectMake(minX,minY, width, height)
VUBarCover.frame = newFrame
If I print out the value of the UIView's frame, I see that it's changing as I like. But the display (in the Simulator) never reflects these changes.
Any idea how to fix this?
Update: In greater detail: I'm trying to programmatically cover up one UIView with another. It's a horizontal "VU Meter", consisting of a base UIImageView showing a color gradient, that gets partially dynamically "covered" up by a UIView with a black background.
The UIImageView "VUBarImageView" is defined in StoryBoard and is subject to AutoLayout. The black UIView "VUBarCover" is defined purely programmatically, with no AutoLayout constraints.
Here's the relevant parts of the code for completeness... The trouble I'm having is in updateVUMeter()...
#IBOutlet weak var VUBarImageView: UIImageView!
var VUBarCover : UIView!
// this gets called after AutoLayout is finished
override func viewDidLayoutSubviews() {
// cover up VU Meter completely
let center = VUBarImageView.center
let width = VUBarImageView.frame.width
let height = VUBarImageView.frame.height
let frame = VUBarImageView.frame
VUBarCover = UIView(frame: frame)
MainView.addSubview(VUBarCover)
VUBarCover.backgroundColor = UIColor.blueColor() // use black eventually. blue lets us see it for testing
}
// partially cover up VUBarImage with black VUBarCover coming from the "right"
func updateVUMeter(inputValue:Float) { //inputValue is in dB
//let val = pow(10.0,inputValue/10) // convert from dB
let val = Float( Double(arc4random())/Double(UInt32.max) ) //for Simulator testing, just use random numbers between 0 and 1
print("val = ",val)
let fullWidth = VUBarImageView.frame.width
let maxX = VUBarImageView.frame.maxX
let width = fullWidth * CGFloat(1.0-val)
let minX = maxX - width
let minY = VUBarImageView.frame.minY
let height = VUBarImageView.frame.height
let newFrame = CGRectMake(minX,minY, width, height)
VUBarCover.frame = newFrame
VUBarCover.setNeedsDisplay()
print("fullWidth = ",fullWidth,"width = ",width)
print(" newFrame = ",newFrame,"VUBarCover.frame = ",VUBarCover.frame)
}
And sample results are:
val = 0.9177
fullWidth = 285.0 width = 23.4556210041046
newFrame = (278.544378995895, 93.5, 23.4556210041046, 10.0) VUBarCover.frame = (278.544378995895, 93.5, 23.4556210041046, 10.0)
val = 0.878985
fullWidth = 285.0 width = 34.4891595840454
newFrame = (267.510840415955, 93.5, 34.4891595840454, 10.0) VUBarCover.frame = (267.510840415955, 93.5, 34.4891595840454, 10.0)
val = 0.955011
fullWidth = 285.0 width = 12.8218790888786
newFrame = (289.178120911121, 93.5, 12.8218790888786, 10.0) VUBarCover.frame = (289.178120911121, 93.5, 12.8218790888786, 10.0)
...i.e., we see that VUBarCover.frame is changing 'internally', but on the screen it never changes. What we see instead is the full-size cover, completely covering the image below (ca. 300 pixels wide), even though its width should be only, about 12 pixels).
Even if I do a setNeedsDisplay() on the superview of VUBarCover, nothing changes.
Help? Thanks.
First, you have a few errors worth noting:
Instance variables (like VUBarCover and MainView should always begin with a lowercase letter (like mainView).
You should call super.viewDidLayoutSubviews() at the beginning of your override implementation
Second, viewDidLayoutSubviews() can be called any number of times, so your implementation of this method should be idempotent, meaning it has an identical effect no matter how many times it's called. But when you call:
VUBarCover = UIView(frame: frame)
MainView.addSubview(VUBarCover)
You are creating a new UIView and adding it to the view hierarchy, along with the others you may have added in the past. So it looks like the frame isn't updating on the screen. In fact, you just can't tell because the old views are behind the one you're updating and they're not changing.
Instead, you probably want to create this once:
var vuBarCover = UIView()
Add it as a subview in viewDidLoad():
mainView.addSubview(vuBarCover)
And modify its frame in viewDidLayoutSubviews():
vuBarCover.frame = vuBarImageView.frame
You don't need to call setNeedsDisplay().
In my case, there were animations left on the layer, after removing them, the frame changes are updated:
myView.layer.removeAllAnimations()
iOS displays presentationLayer of a UIView on the screen, animations are played on that layer (frame updates goes there during animation).
I am making a custom UIView like you right now, maybe I can help.
Firstly, viewDidLayoutSubviews() like your comment: this gets called after AutoLayout is finished. And after every layout did set, you start to set new frame for your UIView and UIImage, maybe the bug is here. Please try to move your code in viewDidLayoutSubviews() to layoutSubviews(). And don't forget add super.layoutSubviews() in override function, which can help you away from hundreds of bugs :].
Secondly, only if you override the drawRect: method, then you should call setNeedsDisplay(), which is telling system that i need to redraw my view, and system will call drawRect: method appropriately.
Hope you can figure it out.

how to set frame dynamically in swift?

i am trying to set imageview as subview for scrollview.for that i am setting different frame depends upon the screensize.
This is my code
let screensize:CGRect=UIScreen.mainScreen().bounds
let screenwidth=screensize.width
var frames:CGRect
for var index=0 ;index < arrBirds.count ;index++
{
if(screenwidth==320)
{
frames.origin.x = CGFloat(index) * (self.view.frame.size.width);
frames.origin.y = 0;
frames.size=scrollview.frame.size;
}
else if(screenwidth==375)
{
frames.origin.x = CGFloat(index) * (self.view.frame.size.width);
frames.origin.y=0;
frames.size=scrollview.frame.size;
}
else
{
frames.origin.x = CGFloat(index) * (self.view.frame.size.width);
frames.origin.y=0;
frames.size=scrollview.frame.size;
}
imageView=UIImageView(frame:frames)
imageView.image=UIImage(named: arrBirds[index] as! String)
scrollview .addSubview(imageView)
But i am getting error this line
frames.origin.x = CGFloat(index) * (self.view.frame.size.width);//struct frame must be completely initialised before a member is stored to.
You have not yet created an instance of the struct CGRect yet. Before accessing / setting any members on it you you need to create a instance first and only after that set the different members of it:
Change the line var frames:CGRect to:
var frames = CGRectZero
According to Apple Docs :
Classes and structures must set all of their stored properties to an
appropriate initial value by the time an instance of that class or
structure is created.
Your frames object isn't initialized, instead you should write :
var frames = CGRectZero

Update the rotation of a CALayer

I am trying to update the current rotation (and sometimes the position) of a CALayer.
What I am trying to in a couple of simple steps:
Store a couple of CALayers in an array, so I can reuse them
Set the anchor point of all CALayers to 0,0.
Draw CALayer objects where the object starts at a position on a circle
The layers are rotated by the same angle as the circle at that position
Update the position and rotation of the CALayer to match new values
Here is a piece of code I have:
lineWidth is the width of a line
self.items is an array containing the CALayer objects
func updateLines() {
var space = 2 * M_PI * Double(circleRadius);
var spaceAvailable = space / (lineWidth)
var visibleItems = [Int]();
var startIndex = items.count - Int(spaceAvailable);
if (startIndex < 0) {
startIndex = 0;
}
for (var i = startIndex; i < self.items.count; i++) {
visibleItems.append(self.items[i]);
}
var circleCenter = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
/* Each line should move up and rotate accordin to this value */
var anglePerLine: CGFloat = (360 / CGFloat(visibleItems.count)).toRadians()
/* Starting position, 270 degrees is on top */
var startAngle: CGFloat = CGFloat(270).toRadians();
/* Lines default rotation, we rotate it to get the right side up */
var lineAngle: CGFloat = CGFloat(180).toRadians();
for (var itemIndex = 0; itemIndex < visibleItems.count; itemIndex++) {
var itemLayer = self.itemLayers[itemIndex];
itemLayer.opacity = 1 - ((0.9 / visibleItems.count) * itemIndex);
/* Calculate start position of layer */
var x = CGFloat(circleRadius) * cos(startAngle) + CGFloat(circleCenter.x);
var y = CGFloat(circleRadius) * sin(startAngle) + CGFloat(circleCenter.y);
var height = CGFloat((arc4random() % 80) + 10);
/* Set position and frame of layer */
itemLayer.frame = CGRectMake(CGFloat(x), CGFloat(y), CGFloat(lineWidth), height);
itemLayer.position = CGPointMake(CGFloat(x), CGFloat(y));
var currentRotation = CGFloat((itemLayer.valueForKeyPath("transform.rotation.z") as NSNumber).floatValue);
var newRotation = lineAngle - currentRotation;
var rotationTransform = CATransform3DRotate(itemLayer.transform, CGFloat(newRotation), 0, 0, 1);
itemLayer.transform = rotationTransform;
lineAngle += anglePerLine;
startAngle += anglePerLine;
}
}
The result of the first run is exactly as I want it to be:
The second run through this code just doesn't update the CALayers correctly and it starts to look like this:
I think it has to do with my code to update the location and transform properties of the CALayer, but whatever I do, it always results in the last picture.
Answered via Twitter: setting frames and transform is mutually exclusive. Happy to help. Finding my login credentials for SO is harder. :D
Found the answer thanks to #iosengineer on Twitter. When setting a position on the CALayer, you do not want to update the frame of the layer, but you want to update the bounds.
Smooth animation FTW

Get frame from certain UISegmentedControl index?

In my app, I use a UIPopoverController and I use the presentPopoverFromRect API. What I am doing now is just setting it to the frame of my whole UISegmentedControl. However I want to be more precise than this. Is there any way to get the frame of a specific index in the UISegmentedControl?
Thanks!
For our project we needed the actual frames of each segment, frame division wasn't enough. Here's a function I wrote that calculates the exact frame for every segment. Be aware that it accesses the segmented control actual subviews, so it might break in any iOS update.
- (CGRect)segmentFrameForIndex:(NSInteger)index inSegmentedControl:(UISegmentedControl *)control
{
// WARNING: This function gets frame from UISegment objects, undocumented subviews of UISegmentedControl.
// May break in iOS updates.
NSMutableArray *segments = [NSMutableArray arrayWithCapacity:self.numberOfSegments];
for (UIView *view in control.subviews) {
if ([NSStringFromClass([view class]) isEqualToString:#"UISegment"]) {
[segments addObject:view];
}
}
NSArray *sorted = [segments sortedArrayUsingComparator:^NSComparisonResult(UIView *a, UIView *b) {
if (a.frame.origin.x < b.frame.origin.x) {
return NSOrderedAscending;
} else if (a.frame.origin.x > b.frame.origin.x) {
return NSOrderedDescending;
}
return NSOrderedSame;
}];
return [[sorted objectAtIndex:index] frame];
}
If the segments are equal, why not just divide the width of the control by the number of the selected segment (+1 because numbering starts at 0)?
EDIT: Like this
-(void)showPopover:(id)sender {
if ((UISegmentedControl*)sender.selectedSegmentIndex == 0)
[self.popover presentPopoverFromRect:CGRectMake(self.segmentedControl.frame.size.width/6, self.segmentedControl.frame.origin.y, aWidth, aHeight)]
}
It's over 6 (I'm assuming a 3 segment implementation), because you have to get the center of the segment, and 3 would put it on the lines. And if you do some simple math here (let's assume the whole control is 60 px wide), then 60/3 yeilds 20. Because each segment is 20 px wide, the width of 60 over six yields the correct answer 10.
This can be done much easier without matching class names, providing that you use no custom segmented control (Swift version)
extension NSSegmentedControl {
func frameForSegment(segment: Int) -> NSRect {
var left : CGFloat = 0
for i in 0..<segmentCount {
let w = widthForSegment(i)
if i == segment {
let off = CGFloat(i) + 2 // Account for separators and border.
return NSRect(x: left + off, y: bounds.minY, width: w, height: bounds.height)
}
left += w
}
return NSZeroRect
}
}
If somebody is searching for a solution for NSSegmentedControl, I've slightly modified #erik-aigner's answer. For UISegmentedControl it should work similarly.
This version improves the geometry computation for spacing and the code in general. Disclaimer: It was tested only for a segmented control placed in a toolbar.
import AppKit
public extension NSSegmentedControl {
/// The width of a single horizontal border.
public static let horizontalBorderWidth: CGFloat = 3
/// The height of a single vertical border.
public static let verticalBorderWidth: CGFloat = 2
/// The horizontal spacing between segments.
public static let horizontalSegmentSpacing: CGFloat = 1
/// Returns the frame of the specified segment.
///
/// - Parameter segment: The index of the segment whose frame should be computed.
/// - Returns: The frame of the segment or `.zero` if an invalid segment index is passed in.
public func frame(forSegment segment: Int) -> NSRect {
let y = bounds.minY - NSSegmentedControl.verticalBorderWidth
let height = bounds.height
var left = NSSegmentedControl.horizontalBorderWidth
for index in 0..<segmentCount {
let width = self.width(forSegment: index)
if index == segment {
return NSRect(x: left, y: y, width: width, height: height)
}
left += NSSegmentedControl.horizontalSegmentSpacing
left += width
}
return .zero
}
}
Swift 5.3 version of #CodaFi's answer:
func showPopover(_ sender: Any?) {
if sender?.selectedSegmentIndex as? UISegmentedControl == 0 {
popover.presentPopover(fromRect: CGRect(x: segmentedControl.frame.size.width / 6, y: segmentedControl.frame.origin.y, width: aWidth, height: aHeight))
}
}
For me widthForSegment returns 0, so the other answers always fail to produce frame. I think a better solution is to just get the frame of a subview:
public extension UISegmentedControl {
func frame(forSegment segment: Int) -> CGRect {
subviews[segment].frame
}
}

Resources