I was writing a program in swift and just now I noticed that I can directly access a CGRect frame's width and height properties directly without using the CGSize width and height. That is I am now able to write a code like this.
#IBOutlet var myView: UIView!
override func viewDidLoad()
{
super.viewDidLoad()
var height = myView.frame.height
var height1 = myView.frame.size.height
}
In Objective C, when I tried to write the same code, the line height = view.frame.height is throwing an error. Can anyone please tell me the difference(if any) in these two lines of code.
I just looked into the CGRect structure reference. In Swift there is an extension defined which have members height and width. Please have a look at the code below
struct CGRect {
var origin: CGPoint
var size: CGSize
}
extension CGRect {
...
var width: CGFloat { get }
var height: CGFloat { get }
...
}
So that you can directly fetch height and width values from a CGRect. As you can see these are only getters, so you will get an error if you try to set these values using view.frame.height = someValue
frame is of CGRect structure, apart from its width and height have only getters, they can only be positive. From the documentation:
Regardless of whether the height is stored in the CGRect data structure as a positive or negative number, this function returns the height as if the rectangle were standardized. That is, the result is never a negative number.
However, size is of CGSize structure, from the documentation:
A CGSize structure is sometimes used to represent a distance vector, rather than a physical size. As a vector, its values can be negative. To normalize a CGRect structure so that its size is represented by positive values, call the standardized function.
So the difference is obvious.
In Objective C, when I tried to write the same code, the line height = view.frame.height is throwing an error. Can anyone please tell me the difference (if any) in these two lines of code.
CGGeometry.h defines a couple of types, among them the C struct CGRect. This struct has two members: origin and size.
That's all you can access in C (and Objective-C) using dot notation. Neither C nor Objective-C offer extensions for structs.
Swift imports the type as a Swift struct. The difference is that Swift does allow for extensions on structs. So it exposes several free C functions as extensions:
CGRectGetMinX() — CGRect.minX
CGRectGetMidX() — CGRect.midX
CGRectGetMaxX() — CGRect.maxX
CGRectGetWidth() — CGRect.width
[... same for y]
These C functions are there since ages—they just live in a dusty corner of CoreGraphics.
They are quite useful but you have to know their semantics (which differ a bit from the standard accessors): They normalise the dimensions.
This means that they convert a rect with negative width or height to a rect that covers the same area with positive size and offset origin.
let rect = CGRect(x: 0, y: 0, width: 10, height: -10)
assert(rect.width == rect.size.width) // OK
assert(rect.height == rect.size.height) // boom
Related
According to my understanding, frame is a view's location and size using the parent view's coordinate system, bounds is a view's location and size using its own coordinate system, which means childView.convert(childView.bounds, to: parentView) should be equal to childView.frame.
But I have found this is not the case for UIPickerTableViewWrapperCell, as you can see from the picture, the frame is (origin = (x = 0, y = 160032.44775782057), size = (width = 134, height = 30.248210824676789)), and the convert(bounds, to: parentView) is (origin = (x = -71.984082796011151, y = 160032.44585526528), size = (width = 134.04199076956854, height = 29.70736933068838))
Why these two values are different?
With a little bit of digging around, I've located an instructive article that may help you understand why this seeming discrepancy is occurring.
For the sake of avoiding a broken link in the future, I'll add the scenario below.
The author begins by taking an example rectangle drawn on the screen. They print out the frame and bounds values with the following function:
private func printAll() {
print("=========================")
print("X : ", self.orangeView.frame.origin.x)
print("Y : ", self.orangeView.frame.origin.y)
print("width : ", self.orangeView.frame.size.width)
print("height : ", self.orangeView.frame.size.height)
print("=========================")
print("X : ", self.orangeView.bounds.origin.x)
print("Y : ", self.orangeView.bounds.origin.y)
print("width : ", self.orangeView.bounds.size.width)
print("height : ", self.orangeView.bounds.size.height)
print("=========================")
}
Using this, they initially begin with an equal width and height:
=========================
X : 87.0
Y : 384.0
width : 240.0
height : 128.0
=========================
X : 0.0
Y : 0.0
width : 240.0
height : 128.0
=========================
They then perform a transformation on the rectangle to rotate it:
self.orangeView.transform = CGAffineTransform(rotationAngle: 5)
This, of course, results in the height and width of the element changing within the parent (.frame) scope:
=========================
X : 111.58938416597198
Y : 314.7747071707769
width : 190.82123166805604
height : 266.45058565844624
=========================
X : 0.0
Y : 0.0
width : 240.0
height : 128.0
=========================
The reason for this, of course, is that by rotating the drawn rectangle, the framing rectangle has changed in size, even though the drawn rectangle has not changed dimensions. Apple is also fully aware of this as indicated in their documentation, stating the following:
Warning
If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.
This is important because we can infer some important behavior from the data you've provided. Specifically, if we ignore the coordinates and instead look at the height and width values, we see the following differences:
frame: (width = 134, height = 30.248210824676789)
bounds: (width = 134, height = 32)
convert: (width = 134.04199076956854, height = 29.70736933068838)
Looking at the UI element itself in the image, we even see text that is being "squished" vertically, indicating that these elements are most likely having transforms applied in some manner. This can result in the the aforementioned undefined behavior indicated in Apple's documentation. And as Apple has indicated in their documentation, when the transform property is not the identity transform, the property's value should be ignored.
Unfortunately we cannot verify that this is the cause of the problem as UIKit, as far as I can tell from digging around, does not share the source code for it, but transforms causing undefined behavior seems like the best explanation we can reasonably arrive at.
I've noticed that in IOS X-Code using (Swift 4.0), I can ask for the height of a view, V, in at least these two ways:
V.bounds.size.height
and...
V.bounds.height
Is there any actual difference between these two?
I did the option-click thing (which give different definitions, but don't explain any practical difference or reason for one over the other)... and stackoverflow... but here on stackoverflow, all the results are discussing the difference between bounds and frame... which is NOT what I'm asking.
V.bounds.height is only a GET Property. You Can't set a value for this property.
Example:
self.view.bounds.height = 5
This error message results...
Cannot assign to property: 'height' is a get-only property
If you want to assign a value to this property, then you can write...
self.view.bounds.size.height = 5
So you can set value to this object. Have a look at here.
There is small difference. view.bounds.height is a shortcut. You cannot edit it :
view.bounds.height = 150 won't work, but view.bounds.size.height = 150 will.
Actually V.bounds.size.height, height have both get-set property and where as in V.bounds.height, height is only getter property and it always return you height of the rectangle.
For the getter perspective both are same.
In addition to the fact that view.bounds.height is readonly, there is another difference: if you have negative width/height, view.bounds.height will return you the normalized value (the positive one), while view.bounds.size.height will return the real value. These getters are the equivalent of the CGRectGetWidth() CGRectGetHeight() from Obj-C. All these getters from CGRect struct (widht, height, minX, minY...) are returning the normalized values of the CGRect's dimensions and they are recommended in case you want to use them in frame computations.
Suppose I want to change size of an uiView: UIView to w,h. I can do it like that:
uiView.frame.size.width = w
uiView.frame.size.height = h
In another system I can avoid replication of dereferencing (which means waste of both size and performance) by keeping a reference in a variable (using Swift syntax):
let ref = uiView.frame.size
ref.width = v
ref.height = h
This however doesn't work in iOS, where CGSize is a structure and therefore is copied when assigned to another value.
Is there a way to avoid redundant dereferencing (something like with(uiView.frame.size){...} available in some languages)
I don't think there is a way to do it exactly because the frame is a value-copied structure. You could set the frame directly as Reiner Melian suggests, but to me that seems even longer and uses dereferencing at least the same amount of time as your approach.
There is a way how to make it simpler this using extensions, but behind the scenes it will again be using dereferencing:
extension UIView {
var width: CGFloat {
get {
return self.frame.size.width
}
set {
self.frame.size.width = newValue
}
}
var height: CGFloat {
get {
return self.frame.size.height
}
set {
self.frame.size.height = newValue
}
}
}
And then you could use:
uiView.width = w
uiView.height = h
on any UIView instance.
This is even simpler:
uiView.frame.size = CGSize(width: w, height: h)
As I understand it, RHS is a temporary value released as soon as the content has been copied to frame structure.
This question is inspired by Andrew Carter's comment on a previous question about the new CGSize initializer in Swift.
The Apple Docs for CGGeometry say:
... your applications
should avoid directly reading and writing the data stored in the
CGRect data structure. Instead, use the functions described here to
manipulate rectangles and to retrieve their characteristics.
Is Apple's recommendation to not directly access the data in a CGRect still valid with Swift? Why should CGRectGetMidX, CGRectGetWidth, etc. be used in place of accessing the values of a CGRect struct directly, when these properties are now exposed with Swift's new extension on CGRect?
Consider a non-standard CGRect with a negative width and height:
var rect = CGRect(x: 0.0, y: 0.0, width: -10.0, height: -10.0)
This is a valid rectangle according to the Apple docs, as "a rectangle with an origin of [0.0, 0.0] and a size of [10.0, 10.0] is exactly equivalent to a rectangle with an origin of [10.0, 10.0] and a size of [-10.0, -10.0]."
You can standardize this CGRect by calling the legacy inline CGRectStandardize method like in Objective-C, or any of the new methods provided on the Swift extension of CGRect:
CGRectStandardize(rect) // {x -10 y -10 w 10 h 10}
rect.standardized // {x -10 y -10 w 10 h 10}
rect.standardizeInPlace() // {x -10 y -10 w 10 h 10}
But wait! This will reposition your rect on the coordinate plane, not only making your width and height positive, but making your origin negative to reflect the initial position of the rect with its negative width and height.
The inline CGRectGet functions provide an interface to normalize a specific value of your rect, without changing its origin. Swift provides an extension on CGRect so you can access the normalized values directly, rather than using the legacy C methods provided by CGGeometry:
var rect = CGRect(x: 0.0, y: 0.0, width: -10.0, height: -10.0)
rect.size.width // non-normalized, returns -10
CGRectGetWidth(rect) // bridged C function, normalized, returns 10
rect.width // new from Swift extension on CGRect, normalized, returns 10
The new interfaces:
extension CGRect {
// ...
public var width: CGFloat { get }
public var height: CGFloat { get }
public var minX: CGFloat { get }
public var midX: CGFloat { get }
public var maxX: CGFloat { get }
public var minY: CGFloat { get }
public var midY: CGFloat { get }
public var maxY: CGFloat { get }
// ...
}
So the answer is yes, the same rules for CGRect in Objective-C apply in Swift as well. The only difference here is that Swift provides an extension on some CGGeometry structs which allow you to move away from the old inline C functions bridged from the CGGeometry headers.
In Swift 3, it looks like CGRectGetWidth() is replaced by CGRect.width.
I have a code but in this code there is an error and I don't know Why i has this error.
I put this code because i want my ball hit least the border soy the screens.
You can see my code :
CGPoint ballCenter = ball.center;
CGRect ballRect = CGRectMake(50, 73, 50, 50); // an arbitrary location for the ball, you would normally use the frame property of the ball here
CGRect s = [UIScreen mainScreen].bounds;
CGRect adjustedScreenRect = CGRectMake(s.x-50 // take the ball's (width or height) and divide it by two to get the distance to the center. Then multiply it by two and subtract from the appropriate dimension(x offset (width), y offset (height), width (width), height (height)).
BOOL isOnScreen = CGRectContainsPoint(adjutedScreenRect, ballCenter);
// do whatever with the BOOL from the above line...
I have an error at this line:
CGRect adjustedScreenRect = CGRectMake(s.x-50 // take the ball's (width or height) and divide it by two to get the distance to the center. Then multiply it by two and subtract from the appropriate dimension(x offset (width), y offset (height), width (width), height (height)).
BOOL isOnScreen = CGRectContainsPoint(adjutedScreenRect, ballCenter);
And the error is "no member named "x" in struct CGRect".
thanks you for your help
The answer is CGRect adjustedScreenRect = CGRectMake(s.origin.x-50
BOOL isOnScreen = CGRectContainsPoint(adjutedScreenRect, ballCenter);
But I have an error Expected ) on BOOL .
Can you help me
I think you mean s.origin.x, not s.x. Because CGRect is a struct of a CGPoint and a CGSize, directly accessing the x value of a CGRect is not possible without specifying which part of the struct you want to access first.
Edit:
You never actually closed the parenthesis, or satisfied all 4 arguments, of the CGRect. Try this:
CGRectMake(s.origin.x-50, s.origin.y, width,height);