iOS frame change one property (eg width)
Asked Answered
B

9

47

This question was originally asked for the objective-c programming language. At the time of writing, swift didn't even exist yet.

Question

Is it possible to change only one property of a CGRect ?

For example:

self.frame.size.width = 50;

instead of

self.frame = CGRectMake(self.frame.origin.x, 
                        self.frame.origin.y, 
                        self.frame.size.width, 
                        50);

of course I understand that self.frame.size.width is read only so I'm wondering how to do this?

CSS ANALOGY proceed at your own risk

for those of you who are familiar with CSS, the idea is very similar to using:

margin-left: 2px;

instead of having to change the whole value:

margin: 5px 5px 5px 2px;
Brade answered 2/3, 2012 at 17:19 Comment(2)
Is self a UIView? If so, then the frame property is not read only. The docs say that if you set it, it will redraw. You should be able to just update one field in it.Riorsson
Also check .offsetBy function https://mcmap.net/q/271859/-simple-way-to-change-the-position-of-uiviewLavone
J
106

To answer your original question: yes, it's possible to change just one member of a CGRect structure. This code throws no errors:

myRect.size.width = 50;

What is not possible, however, is to change a single member of a CGRect that is itself a property of another object. In that very common case, you would have to use a temporary local variable:

CGRect frameRect = self.frame;
frameRect.size.width = 50;
self.frame = frameRect;

The reason for this is that using the property accessor self.frame = ... is equivalent to [self setFrame:...] and this accessor always expects an entire CGRect. Mixing C-style struct access with Objective-C property dot notation does not work well in this case.

Jelks answered 2/3, 2012 at 17:26 Comment(4)
@OleBegermann the best part is that you(43k) say yes and Milos(26) says no. I just don't know what to do!Brade
As I said, it generally works but it does not if the CGRect is a property of an Objective-C object, as in your example. Milos was probably referring to your sample code and is correct in that regard.Jelks
Just going to mention this - self.frame.size.width = 50 works in Swift.Hexastyle
Further, I believe my nearly two year old answer is more useful if you're stuck using Obj-C: https://mcmap.net/q/359912/-ios-frame-change-one-property-eg-widthHexastyle
H
29

I liked Ahmed Khalaf's answer, but it occurred to me that you may as well just write out a few C functions... the key advantage being that it'll be easier to track down errors in the event that you're using the wrong type.

Having said that, I wrote a .h file with these function declarations:

CGRect CGRectSetWidth(CGRect rect, CGFloat width);
CGRect CGRectSetHeight(CGRect rect, CGFloat height);
CGRect CGRectSetSize(CGRect rect, CGSize size);
CGRect CGRectSetX(CGRect rect, CGFloat x);
CGRect CGRectSetY(CGRect rect, CGFloat y);
CGRect CGRectSetOrigin(CGRect rect, CGPoint origin);

And a corresponding .m file with these function implementations:

CGRect CGRectSetWidth(CGRect rect, CGFloat width) {
    return CGRectMake(rect.origin.x, rect.origin.y, width, rect.size.height);
}

CGRect CGRectSetHeight(CGRect rect, CGFloat height) {
    return CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, height);
}

CGRect CGRectSetSize(CGRect rect, CGSize size) {
    return CGRectMake(rect.origin.x, rect.origin.y, size.width, size.height);
}

CGRect CGRectSetX(CGRect rect, CGFloat x) {
    return CGRectMake(x, rect.origin.y, rect.size.width, rect.size.height);
}

CGRect CGRectSetY(CGRect rect, CGFloat y) {
    return CGRectMake(rect.origin.x, y, rect.size.width, rect.size.height);
}

CGRect CGRectSetOrigin(CGRect rect, CGPoint origin) {
    return CGRectMake(origin.x, origin.y, rect.size.width, rect.size.height);
}

So, now, to do what you want, you can just do:

self.frame = CGRectSetWidth(self.frame, 50);

Get even Fancier (update I made a year later)

This has a redundant self.frame in it, though. To fix that, you could add a category on UIView with methods that look like this:

- (void) setFrameWidth:(CGFloat)width {
    self.frame = CGRectSetWidth(self.frame, width); // You could also use a full CGRectMake() function here, if you'd rather.
}

And now you can just type in:

[self setFrameWidth:50];

Or, even better:

self.frameWidth = 50;

And just so you can do something like this:

self.frameWidth = otherView.frameWidth; // as opposed to self.frameWidth = otherView.frame.size.width;

You'll need to also have this in your category:

- (CGFloat) frameWidth {
    return self.frame.size.width;
}

Enjoy.

Hexastyle answered 2/1, 2013 at 3:56 Comment(6)
As an extra bonus over Ahmed's method of using #define... it was pointed out to me a week or two ago that #define is rather evil. It's not context sensitive and so if you have something like #define my 8, it'll replace something like myRect with 8Rect... henceforth, I've decided I just won't be using #define.Hexastyle
There is a time and a place for #define, but for the reasons you've mentioned it just makes sense to pick definition names that are guaranteed to be unique within your code, e.g. #define MY_SUBCLASS_LEFT_MARGIN 10Culbert
@CarlosP No. const int MY_SUBCLASS_LEFT_MARGIN = 10; - the only time you should ever use #define is for something that can only be known at compile time, for example, details about the compiler or which version is being compiled might be defines. Suppose you're working on a large project and someone elsewhere has #define MARGIN 8. Now your defines are broken and the build fails. const was added for a reason, and I need to bash that into so many C, C++, and Obj-C programmers heads for some reason because they're so determined to misuse CPP's #define.Hexastyle
That would never happen. As I mentioned clearly above I ensure that any #defines I make have a unique name specific to, and only to, the class they're defined in. So I certainly wouldn't ever type #define MARGIN.. That said, I probably do over-use #define for convenience/speed and take your comment on board.Culbert
@CarlosP - That's nice that you have your own convention. Have you ever worked with another person? Two other people? Ten other people? More? Eventually, someone is going to come in and break your carefully thought out #define naming conventions. const was added to C89 24 years ago - before I was born - please use it. Not using it is like using a while() loop that has an initialization before it and an incremented at the end... yes, that works, but for loop exists for a reason, and ignoring it will lead to easily avoided, but difficult to track down, bugs later on.Hexastyle
You make a good point, if rather robustly. It's good that we've both learned something.Culbert
I
6

Based on ArtOfWarfare's solution (which is really awesome) I've build the UIView category without C-functions.

Usage examples:

[self setFrameWidth:50];
self.frameWidth = 50;
self.frameWidth += 50;
self.frameWidth = otherView.frameWidth; // as opposed to self.frameWidth = otherView.frame.size.width;

Header file UIView+easy_frame.h:

@interface UIView (easy_frame)

- (void) setFrameWidth:(CGFloat)width;
- (void) setFrameHeight:(CGFloat)height;
- (void) setFrameX:(CGFloat)x;
- (void) setFrameY:(CGFloat)y;

- (CGFloat) frameWidth;
- (CGFloat) frameHeight;
- (CGFloat) frameX;
- (CGFloat) frameY;

Implementation file UIView+easy_frame.m:

#import "UIView+easy_frame.h"
@implementation UIView (easy_frame)

# pragma mark - Setters

- (void) setFrameWidth:(CGFloat)width
{
  self.frame = CGRectMake(self.frame.origin.x,
                          self.frame.origin.y,
                          width,
                          self.frame.size.height);
}

- (void) setFrameHeight:(CGFloat)height
{
  self.frame = CGRectMake(self.frame.origin.x,
                          self.frame.origin.y,
                          self.frame.size.width,
                          height);
}

- (void) setFrameX:(CGFloat)x
{
  self.frame = CGRectMake(x,
                          self.frame.origin.y,
                          self.frame.size.width,
                          self.frame.size.height);
}

- (void) setFrameY:(CGFloat)y
{
  self.frame = CGRectMake(self.frame.origin.x,
                          y,
                          self.frame.size.width,
                          self.frame.size.height);
}

# pragma mark - Getters

- (CGFloat) frameWidth
{
  return self.frame.size.width;
}

- (CGFloat) frameHeight
{
  return self.frame.size.height;
}

- (CGFloat) frameX
{
  return self.frame.origin.x;
}

- (CGFloat) frameY
{
  return self.frame.origin.y;
}
Insipid answered 5/9, 2014 at 10:36 Comment(0)
F
4

If you find yourself needing to do this sort of individual component modification, it may be worthwhile having macros like these somewhere accessible by all of your code:

#define CGRectSetWidth(rect, w)    CGRectMake(rect.origin.x, rect.origin.y, w, rect.size.height)
#define ViewSetWidth(view, w)   view.frame = CGRectSetWidth(view.frame, w)

This way, whenever you need to change the width alone - you would simply write

ViewSetWidth(self, 50);

instead of

self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, 50);
Flatulent answered 19/9, 2012 at 0:45 Comment(1)
Very nice suggestion, although I only added in CGRectSetWidth (along with setters for height, x, and y.) It seemed to me that mixing CPP syntax with Obj-C syntax would look confusing...Hexastyle
F
2

Based on n0_quarter answer, here's a UIView Extension written in Swift:

/**
*  Convenience category for manipulating UIView frames
*/
extension UIView {

    //MARK: - Getters
    func frameX() -> CGFloat {
        return frame.origin.x
    }

    func frameY() -> CGFloat {
        return frame.origin.y
    }

    func frameWidth() -> CGFloat {
        return frame.size.width
    }

    func frameHeight() -> CGFloat {
        return frame.size.height
    }

    //MARK: - Setters
    func setFrameX(x: CGFloat) {
        frame = CGRect(x: x, y: frame.origin.y, width: frame.size.width, height: frame.size.height)
    }

    func setFrameY(y: CGFloat) {
        frame = CGRect(x: frame.origin.x, y: y, width: frame.size.width, height: frame.size.height)
    }

    func setFrameWidth(width: CGFloat) {
        frame = CGRect(x: frame.origin.x, y: frame.origin.y, width: width, height: frame.size.height)
    }

    func setFrameHeight(height: CGFloat) {
        frame = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.size.width, height: height)
    }
}
Fernandafernande answered 16/9, 2014 at 20:28 Comment(1)
So this is based on n0_quarter's, which was based on mine, which was based on arkuana's. The answer has now gone from CPP to C to Obj-C to Swift.Hexastyle
A
2

In Swift you could extend the UIView class for the app, such that you could, for example, move the table header view up by 10 points like this:

    tableView.tableHeaderView!.frameAdj(0, -20, 0, 0)

(of course if you're using AutoLayout, that might not actually do anything... :) )

By extending UIView (affecting every UIView and subclass of it in your app)

extension UIView {
    func frameAdj(x: CGFloat, _ y: CGFloat, _ width: CGFloat, _ height: CGFloat) {
            self.frame = CGRectMake(
                self.frame.origin.x + x,
                self.frame.origin.y + y,
                self.frame.size.width  + width,
                self.frame.size.height + height)
    }
}
Afterbody answered 13/2, 2015 at 17:14 Comment(0)
S
0

Hope I am not too late to the party, here's my solution in ObjC.

For me I prefer to stick to a single function instead of several functions, and I like the CSS approach shown by the poster. Below is the function I add to my UIView Category.

- (void)resetFrame:(CGRect)frame {
CGRect selfFrame = self.frame;
selfFrame.origin = CGPointMake(frame.origin.x>0 ? frame.origin.x : selfFrame.origin.x, frame.origin.y>0 ? frame.origin.y : selfFrame.origin.y);
selfFrame.size = CGSizeMake(frame.size.width>0 ? frame.size.width : selfFrame.size.width, frame.size.height>0 ? frame.size.height : selfFrame.size.height);
[self setFrame:selfFrame]; }

Shown in the example, it looks at each values in the provided CGRect, and if the value is more than 0, it means we want to replace that value. And therefore, you can do CGRect(0,0,100,0) and it will assume we want to replace the width only.

Sublunary answered 20/11, 2015 at 4:11 Comment(0)
C
0

frequently call setFrame method is not so good, like: setFrameX(x) setFrameY(y) setFrameWidth(width) ...

instead, in swift we can make use of default parameters to set multiple parameters only in one call:

func setFrame(x x: CGFloat = CGFloat.NaN, y: CGFloat = CGFloat.NaN, width: CGFloat = CGFloat.NaN, height: CGFloat = CGFloat.NaN) {
    let newX = x.isNaN ? self.frame.origin.x : x
    let newY = y.isNaN ? self.frame.origin.y : y
    let newWidth = width.isNaN ? self.bounds.size.width : width
    let newHeight = height.isNaN ? self.bounds.size.height : height

    self.frame = CGRect(x: newX, y: newY, width: newWidth, height: newHeight)
}

then update parameters of frame only need:

setFrame(x: x, width: min(maxX - x, titleLabel.bounds.size.width))
setFrame(width: titleDescriptionLabel.bounds.size.width + 5, height: titleDescriptionLabel.bounds.size.height + 6)
Chlorpromazine answered 10/12, 2015 at 7:37 Comment(0)
E
-1
-(void) sizeScreen : (UIView *) size : (double) width : (double) height : (double) x : (double) y {
CGRect frameRect = size.frame;
frameRect.size.width = width;
frameRect.size.height = height;
frameRect.origin.x = x;
frameRect.origin.y = y;
self.view.frame = frameRect;

}

its work great for me :)

Estefanaestel answered 3/9, 2018 at 8:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.