首页 » iOS编程(第4版) » iOS编程(第4版)全文在线阅读

《iOS编程(第4版)》20.5 修改BNRItemCell

关灯直达底部

现在UITableView的行高已经可以根据首选字体大小动态改变了,但是BNRItemCell中UILabel对象的文字大小还不会发生任何变化。本节将修改BNRItemCell,支持动态字体。首先查看BNRItemCell各个子视图的现有约束:UIImageView对象位于contentView左边,并保持垂直居中;nameLabel始终位于顶部,serialNumberLabel始终位于底部;valueLabel位于contentView右边,也保持垂直居中(见图20-5)。

图20-5 BNRItemCell各个子视图的现有约束

下面根据用户首选字体设置UILabel对象的font属性,读者可以参考之前在BNRDetailViewController或BNRItemsViewController中添加的代码。

打开BNRItemCell.m,添加下列方法:

- (void)updateInterfaceForDynamicTypeSize

{

UIFont *font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];

self.nameLabel.font = font;

self.serialNumberLabel.font = font;

self.valueLabel.font = font;

}

- (void)awakeFromNib

{

[self updateInterfaceForDynamicTypeSize];

NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];

[nc addObserver:self

selector:@selector(updateInterfaceForDynamicTypeSize)

name:UIContentSizeCategoryDidChangeNotification

object:nil];

}

- (void)dealloc

{

NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];

[nc removeObserver:self];

}

以上代码只有awakeFromNib与之前有所区别。当NIB文件解档某个对象后,该对象就会收到awakeFromNib消息,可以在awakeFromNib中设置该对象,或者执行其他需要的初始化操作。例如,以上代码在awakeFromNib中设置了UILabel对象的font属性,并将BNRItemCell对象注册为UIContentSizeCategoryDidChangeNotification的观察者。构建并运行应用,现在BNRItemCell可以根据用户首选字体改变界面中的文字大小了。

接下来需要解决BNRItemCell中的一些自动布局问题。

与之前BNRDetailViewController中的问题类似,第19章限定了nameLabel和serialNumberLabel的高度,自动布局系统将无法修改两个UILabel对象的高度。

打开BNRItemCell.xib,删除两个UILabel对象的高度约束,这时Interface Builder会提示视图位置错误。打开Resolve Auto Layout Issues菜单,选择Update All Frames in Homepwner Item Cell。构建并运行应用,现在UILabel对象会根据用户首选字体自动调整高度。

最后还剩下UIImageView对象。下面将修改UIImageView对象的约束,使UIImageView对象可以根据用户首选字体自动调整大小。

为约束添加插座变量

如果需要修改某个视图的大小或位置,无论是修改为固定值还是修改与其他视图之间的相对关系,都应该修改视图的约束,而不是硬编码视图的frame;否则,当界面需要重新布局时,自动布局系统仍然会根据之前的约束修改视图的frame,从而覆盖硬编码的frame。

为了在运行时根据用户首选字体动态修改UIImageView对象的宽度和高度约束,需要为两个约束添加相应的插座变量。约束是NSLayoutConstraint类的对象,添加插座变量的过程与其他视图对象是相同的。

在BNRItemCell.m的类扩展中,添加并关联宽度和高度约束的插座变量,代码如下:

@interface BNRItemCell ()

@property (nonatomic, weak) IBOutlet NSLayoutConstraint

*imageViewHeightConstraint;

@property (nonatomic, weak) IBOutlet NSLayoutConstraint

*imageViewWidthConstraint;

@end

现在可以在代码中动态修改UIImageView对象的两个约束了。在BNRItemCell.m中,修改updateInterfaceForDynamicTypeSize,获取用户首选字体大小,然后修改宽度和高度约束的限定值(设置NSLayoutConstraint对象的constant属性)。

- (void)updateInterfaceForDynamicTypeSize

{

UIFont *font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];

self.nameLabel.font = font;

self.serialNumberLabel.font = font;

self.valueLabel.font = font;

static NSDictionary *imageSizeDictionary;

if (!imageSizeDictionary) {

imageSizeDictionary = @{

UIContentSizeCategoryExtraSmall : @40,

UIContentSizeCategorySmall : @40,

UIContentSizeCategoryMedium : @40,

UIContentSizeCategoryLarge : @40,

UIContentSizeCategoryExtraLarge : @45,

UIContentSizeCategoryExtraExtraLarge : @55,

UIContentSizeCategoryExtraExtraExtraLarge : @65

};

}

NSString *userSize =

[[UIApplication sharedApplication] preferredContentSizeCategory];

NSNumber *imageSize = imageSizeDictionary[userSize];

self.imageViewHeightConstraint.constant = imageSize.floatValue;

self.imageViewWidthConstraint.constant = imageSize.floatValue;

}

构建并运行应用,修改用户首选字体,这时UIImageView对象会根据用户首选字体自动调整大小。同时,由于之前限定了nameLabel和serialNumberLabel左边与UIImageView对象右边的距离,因此当UIImageView对象的大小发生变化时,nameLabel和serialNumberLabel也会自动调整位置,保持与UIImageView对象的间距。如果之前限定的是两个UILabel对象左边与父视图的距离,那么UIImageView对象就可能遮住两个UILabel对象,或者留下大片空白。

现在BNRItemCell可以很好地支持动态字体了,但是还需要做最后一处修改。

占位符约束

以上代码同时修改了UIImageView对象的宽度和高度约束,虽然这样做没有问题,但是还有一种更好的解决方案:只修改UIImageView对象的高度约束,然后添加另一个约束,限定UIImageView对象的宽度与高度相同。该约束无法在Interface Builder中添加,必须通过代码创建。回到BNRItemCell.m,在awakeFromNib中为UIImageView对象(thumbnailView属性)添加该约束,代码如下:

- (void)awakeFromNib

{

[self updateInterfaceForDynamicTypeSize];

NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];

[nc addObserver:self

selector:@selector(updateInterfaceForDynamicTypeSize)

name:UIContentSizeCategoryDidChangeNotification

object:nil];

NSLayoutConstraint *constraint =

[NSLayoutConstraint constraintWithItem:self.thumbnailView

attribute:NSLayoutAttributeHeight

relatedBy:NSLayoutRelationEqual

toItem:self.thumbnailView

attribute:NSLayoutAttributeWidth

multiplier:1

constant:0];

[self.thumbnailView addConstraint:constraint];

}

现在可以删除imageViewWidthConstraint属性和相关代码:

@interface BNRItemCell ()

@property (nonatomic, weak) IBOutlet NSLayoutConstraint

*imageViewHeightConstraint;

@property (nonatomic, weak) IBOutlet NSLayoutConstraint

*imageViewWidthConstraint;

@end

@implementation

- (void)updateInterfaceForDynamicTypeSize

{

// 这里省略了其他代码……

NSNumber *imageSize = imageSizeDictionary[userSize];

self.imageViewHeightConstraint.constant = imageSize.floatValue;

self.imageViewWidthConstraint.constant = imageSize.floatValue;

}

打开BNRItemCell.xib,删除UIImageView对象与imageViewWidthConstraint的关联。

最后还有一个问题:UIImageView对象现在有两个宽度约束。除了以上代码创建的之外,XIB文件中也添加过宽度约束。如果在运行时UIImageView对象的宽度发生变化,就会造成约束冲突。

为了解决该问题,需要删除Interface Builder中添加的宽度约束,但是这么做会导致Interface Builder提示视图位置错误或者布局有歧义。因此,更好的方法是将宽度约束设置为占位符约束(placeholder constraint)。自动布局系统会在构建时移除占位符约束,占位符约束并不会真正对视图起作用。

在BNRItemCell.xib中选中UIImageView对象的宽度约束,然后打开属性检视面板,选中标题为Placeholder的选择框。Placeholder选择框后面的描述也解释了“Placeholder”的含义:Remove at build time(在构建时移除),如图20-6所示。构建并运行应用,界面不会有任何变化,但是现在UIImageView对象的宽度约束是根据高度约束定义的。

图20-6 占位符约束