Кадр iOS изменяет одно свойство (например, ширину)

Этот вопрос изначально был задан для языка программирования Objective-C. На момент написания этой статьи Swift еще даже не существовал.

Вопрос

Можно ли изменить только одно свойство CGRect ?

Например:

self.frame.size.width = 50;

вместо

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

конечно, я понимаю, что self.frame.size.width только для чтения, поэтому мне интересно, как это сделать?

АНАЛОГИЯ CSS действуйте на свой страх и риск

для тех из вас, кто знаком с CSS, идея очень похожа на использование:

margin-left: 2px;

вместо того, чтобы изменить все значение:

margin: 5px 5px 5px 2px;

person Jacksonkr    schedule 02.03.2012    source источник
comment
Является ли self UIView? Если да, то свойство фрейма доступно не только для чтения. Документы говорят, что если вы установите его, он будет перерисовываться. Вы должны иметь возможность просто обновить одно поле в нем.   -  person user1118321    schedule 02.03.2012
comment
Также проверьте функцию .offsetBy stackoverflow.com/a/43380510/5790492.   -  person Nik Kov    schedule 21.04.2020


Ответы (9)


Чтобы ответить на ваш первоначальный вопрос: да, можно изменить только один член структуры CGRect. Этот код не выдает ошибок:

myRect.size.width = 50;

Однако невозможно изменить один элемент CGRect, который сам является свойством другого объекта. В этом очень распространенном случае вам придется использовать временную локальную переменную:

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

Причина этого в том, что использование метода доступа к свойству self.frame = ... эквивалентно [self setFrame:...], и этот метод доступа всегда ожидает целое CGRect. Смешивание доступа struct в стиле C с точечной записью свойства Objective-C в этом случае не работает.

person Ole Begemann    schedule 02.03.2012
comment
@OleBegermann Самое приятное то, что вы (43k) говорите «да», а Милош (26) говорит «нет». Я просто не знаю, что делать! - person Jacksonkr; 02.03.2012
comment
Как я уже сказал, обычно это работает, но нет, если CGRect является свойством объекта Objective-C, как в вашем примере. Милош, вероятно, имел в виду ваш образец кода и прав в этом отношении. - person Ole Begemann; 02.03.2012
comment
Просто упомяну об этом - self.frame.size.width = 50 работает в Swift. - person ArtOfWarfare; 11.09.2014
comment
Кроме того, я считаю, что мой почти двухлетний ответ более полезен, если вы застряли с использованием Obj-C: stackoverflow.com/a/14116702 /901641 - person ArtOfWarfare; 11.09.2014

Мне понравился ответ Ахмеда Халафа, но мне пришло в голову, что вы можете просто написать несколько функций C... главное преимущество заключается в том, что будет легче отслеживать ошибки, если вы используете неправильный тип.

Сказав это, я написал файл .h с этими объявлениями функций:

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);

И соответствующий файл .m с этими реализациями функций:

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);
}

Итак, теперь, чтобы делать то, что вы хотите, вы можете просто сделать:

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

Стать еще Fancier (обновление, которое я сделал год спустя)

Однако в нем есть избыточный self.frame. Чтобы исправить это, вы можете добавить category к UIView с помощью методов, которые выглядят следующим образом:

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

И теперь вы можете просто ввести:

[self setFrameWidth:50];

Или, что еще лучше:

self.frameWidth = 50;

И просто так вы можете сделать что-то вроде этого:

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

Вам также необходимо иметь это в своей категории:

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

Наслаждаться.

person ArtOfWarfare    schedule 02.01.2013
comment
В качестве дополнительного бонуса к методу Ахмеда с использованием #define... неделю или две назад мне было указано, что #define довольно зло. Это не зависит от контекста, поэтому, если у вас есть что-то вроде #define my 8, оно заменит что-то вроде myRect на 8Rect... впредь я решил, что просто не буду использовать #define. - person ArtOfWarfare; 27.01.2013
comment
Есть время и место для #define, но по причинам, которые вы упомянули, просто имеет смысл выбрать имена определений, которые гарантированно будут уникальными в вашем коде, например. #define MY_SUBCLASS_LEFT_MARGIN 10 - person Carlos P; 29.07.2013
comment
@CarlosP Нет. const int MY_SUBCLASS_LEFT_MARGIN = 10; - единственный раз, когда вы должны использовать #define, - это то, что может быть известно только во время компиляции, например, сведения о компиляторе или о том, какая версия компилируется, могут быть определены. Предположим, вы работаете над большим проектом, и у кого-то еще есть #define MARGIN 8. Теперь ваши определения сломаны, и сборка завершается неудачно. const был добавлен по какой-то причине, и по какой-то причине мне нужно вбить это в голову стольким программистам C, C++ и Obj-C, потому что они так полны решимости неправильно использовать #define CPP. - person ArtOfWarfare; 29.07.2013
comment
Этого никогда не случится. Как я ясно упомянул выше, я гарантирую, что любые #define, которые я делаю, имеют уникальное имя, специфичное для класса, в котором они определены, и только для него. Так что я, конечно, никогда не наберу #define MARGIN. -используйте #define для удобства/скорости и примите во внимание ваш комментарий. - person Carlos P; 29.07.2013
comment
@CarlosP - Хорошо, что у тебя есть собственное соглашение. Вы когда-нибудь работали с другим человеком? Два других человека? Еще десять человек? Более? В конце концов, кто-то придет и нарушит ваши тщательно продуманные #define соглашения об именах. const был добавлен в C89 24 года назад - до моего рождения - пожалуйста, используйте его. Неиспользование его похоже на использование цикла while(), который имеет инициализацию перед ним и инкремент в конце... да, это работает, но цикл for существует по какой-то причине, и его игнорирование приведет к легко избегаемому, но сложному чтобы отследить, ошибки позже. - person ArtOfWarfare; 29.07.2013
comment
Вы делаете хорошее замечание, хотя и довольно сильно. Хорошо, что мы оба чему-то научились. - person Carlos P; 29.07.2013

На основе решения ArtOfWarfare (это действительно потрясающе) я создал категорию UIView без C-функций.

Примеры использования:

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

Заголовочный файл 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;

Файл реализации 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;
}
person n0_quarter    schedule 05.09.2014

Если вы обнаружите, что вам нужно выполнить такую ​​модификацию отдельных компонентов, возможно, стоит иметь такие макросы где-то, доступные для всего вашего кода:

#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)

Таким образом, всякий раз, когда вам нужно изменить только ширину, вы просто напишете

ViewSetWidth(self, 50);

вместо

self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, 50);
person pxlshpr    schedule 19.09.2012
comment
Очень хорошее предложение, хотя я добавил только CGRectSetWidth (вместе с сеттерами для высоты, x и y). Мне казалось, что смешивание синтаксиса CPP с синтаксисом Obj-C будет выглядеть запутанным... - person ArtOfWarfare; 02.01.2013

Основываясь на ответе n0_quarter, вот расширение UIView, написанное на 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)
    }
}
person Robert Wagstaff    schedule 16.09.2014
comment
Так что это основано на n0_quarter, который был основан на моем, который был основан на arkuana. Теперь ответ перешел от CPP к C, затем к Obj-C и Swift. - person ArtOfWarfare; 02.01.2015

В Swift вы можете расширить класс UIView для приложения, чтобы вы могли, например, переместить представление заголовка таблицы на 10 пунктов, например:

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

(конечно, если вы используете AutoLayout, это может ничего не делать... :))

Расширяя UIView (влияя на каждый UIView и его подклассы в вашем приложении)

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)
    }
}
person clearlight    schedule 13.02.2015

Надеюсь, я не опоздал на вечеринку, вот мое решение в ObjC.

Что касается меня, я предпочитаю придерживаться одной функции, а не нескольких, и мне нравится подход CSS, показанный на постере. Ниже приведена функция, которую я добавляю в свою категорию UIView.

- (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]; }

Как показано в примере, он просматривает каждое значение в предоставленном CGRect, и если значение больше 0, это означает, что мы хотим заменить это значение. И, следовательно, вы можете сделать CGRect(0,0,100,0) и предположить, что мы хотим заменить только ширину.

person wahkiz    schedule 20.11.2015

часто вызывать метод setFrame не так уж и хорошо, например: setFrameX(x) setFrameY(y) setFrameWidth(width) ...

вместо этого в swift мы можем использовать параметры по умолчанию, чтобы установить несколько параметров только в одном вызове:

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)
}

тогда нужно только обновить параметры фрейма:

setFrame(x: x, width: min(maxX - x, titleLabel.bounds.size.width))
setFrame(width: titleDescriptionLabel.bounds.size.width + 5, height: titleDescriptionLabel.bounds.size.height + 6)
person Bigyelow    schedule 10.12.2015

-(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;

}

его работа велика для меня :)

person DavidoM    schedule 03.09.2018