Avoid redundant dereferencing in iOS - ios

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.

Related

How to "list" text in columns in iOS?

Basically, I wish to list some text like so, in a column view:
Item Colour Quantity
Glasses Black 3
It's not for user-input, more towards the user reading it. I know this can be accomplished via the use of labels, however, this does tend to get tedious. I have the text written up in a word document, tried to copy and paste it into a UITextView and mess around with the attributes, I get 99% close however the last "column" seems to always give me an issue.
Can anyone potentially shed some light onto how I can do this with ease?
P.S- I will probably have 10 "lists" each different.
You have basically three good possibilities.
1) You give it a try using labels
2) You take a UICollectionView or
3) some UITableViews side by side
I had the same problem and I decided to go with option 1)
Create a method which needs a 2d String array as input.
Determine the ScreenSize of the device.
func createRows(textName: [[String]])
{
dispatch_async(dispatch_get_main_queue())
{
if self.columnArrayActive || self.columnArray1.isEmpty
{
let screenSize: CGRect = UIScreen.mainScreen().bounds
let screenWidth = screenSize.width
self.ScrollViewMain.contentSize.height = CGFloat(30 * textName.count))
Now create two for loops to iterate through the array.
Create the labels and modify them.
for i1 in 0..<textName.count
{
var columnArray = [UILabel]()
for i2 in 0..<textName[i1].count
{
columnArray.append((UILabel(frame: CGRectMake(CGFloat((Int(screenWidth) / 9) * i2 - Int(screenWidth / 128)), (CGFloat(30 * i1) - modifyY), CGFloat(Int(screenWidth) / 8), 30))))
columnArray[i2].text = textName[i1][i2]
columnArray[i2].textAlignment = NSTextAlignment.Center
I have additionally structured the rows with two different colors for a better reading experience.
if i1 == 0
{
columnArray[i2].backgroundColor = UIColor(red: CGFloat(1), green: CGFloat(0.35), blue: CGFloat(0.35), alpha: CGFloat(1))
} else {
if self.rowSwitcher
{ columnArray[i2].backgroundColor = settings.vari.color1 as? UIColor}
else
{ columnArray[i2].backgroundColor = settings.vari.color2 as? UIColor}
}
self.scrollViewMain.addSubview(columnArray[i2])
}
if i1 != 0
{
if self.rowSwitcher
{ self.rowSwitcher = false }
else
{ self.rowSwitcher = true }
}
self.columnArray1.append(columnArray)
}
self.columnArrayActive = false
This part of the code is used when you already have created your list but you need to update the values of the labels.
} else {
for i3 in 0..<textName.count
{
for i4 in 0..<textName[i3].count
{
self.columnArray1[i3][i4].text = textName[i3][i4]
}
}
}
}
}
You need to define some global or class variables to get it work:
1)columnArray1 : [[UILabel]]
2)columnArrayActive : Bool
3)scrollViewMain : UIScrollView!
You create in the Interface Builder a UIScrollView filling you screen.
Then you create constraints.
Afterwards you create a reference to your class and in viewDidLoad you add:
scrollViewMain = self.delegate
This means that your class needs of course to inherit from UIScrollViewDelegate!
The result looks like this:
Don't forget you can draw text into a graphics context. For read-only text, this is definitely an option I would consider. Another option which might work for you is text containers in Text Kit.

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

Using variables in CGRectMake, Swift, UIkit

For an App I'm making i need to use variables to change the size and position of objects (Labels). I've tried var example = CGRectMake(0, 0, 0, 100), hoping it would ignore the zeros (Not really thinking it would though). I then tried:
var example = 100
Label1.frame = CGRectMake(20, 20, 50, example)
I changed the syntax a bit, adding "" and replacing the CGRectMake with CGRect etc, but nothing worked... I don't get what I'm doing wrong here... Help!
Below is the new syntax used since Swift 3.
CGRect(x: 0, y: 0, width: 100, height: 100)
CGRectMake takes CGFloats for all of its arguments. Your sample code should work fine if you specify that example is supposed to be a CGFloat, using a type identifier:
// v~~~~ add this...
var example: CGFloat = 100
Label1.frame = CGRectMake(20, 20, 50, example)
Otherwise, swift infers the type of example to be Int, and the call to CGRectMake fails, cuz it can't take an Int as a parameter...
So, there is many ways to skin the cat. It all depends what your needs and requirements are (maybe you could elaborate a bit on what you are trying to achieve?). But one way to do it could be to set a variable when something happens, and then update the frame of the label. If you added a tap gesture recognizer to your view, and updated your label like so:
let myLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
let tapGestRecog = UITapGestureRecognizer(target: self, action: "handleTap:")
self.view.addGestureRecognizer(tapGestRecog)
}
func handleTap(sender:UIGestureRecognizer) {
let newXposition = sender.locationInView(self.view).x
let newYposition = sender.locationInView(self.view).y
myLabel.frame = CGRectMake(newXposition, newYposition, 200, 200)
}
This is just an example, and a very crude way of doing it. There are many other ways of doing it, but it hopefully gives you an idea of how to achieve it.
Swift allows syntax that Objective-C does not:
var example = 100
label.frame.size.height = example
In objective-C you would have to do it differently:
CGRect frame = label.frame; //Create a temporary rect to hold the frame value
frame.size.height = example;
label.frame = frame;

Difference between frame.size.width and frame.width

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

Setter is not working in Swift

I'm trying to extend UIView with shortcuts to setting size and origin, as done in Obj-C here.
Getters work great, but setters don't. Any ideas why? What am I doing wrong?
Here is my code rewritten from Obj-C to Swift:
extension UIView {
...
var height : CGFloat {
get {
return self.frame.size.height
}
set {
var r = self.frame;
r.size.height = height;
self.frame = r;
}
}
And here is another one, since CGRect properties are "val"
var width : CGFloat {
set {
self.frame.size.width = width
}
get {
return self.frame.size.width
}
}
But none of this setters works
This works great, but it's too long :)
self.frame.size.width = 100
Thank you for you help!
The setter name and enclosing parentheses is optional. If you provide
a setter name, it is used as the name of the parameter to the setter.
If you do not provide a setter name, the default parameter name to the
setter is newValue.
Change your set method to:
set {
self.frame.size.width = newValue
}

Resources