草庐IT

ios - 父 View 框架尺寸更改后更新约束

coder 2023-09-20 原文

我有一个非常简单的 UIViewController,我用它来尝试更好地理解约束、自动布局和框架。 View Controller 有两个 subview :两者都是 UIView,它们根据设备方向并排或顶部/底部。在每个 UIView 中,都存在一个应该在其父 View 中居中的标签。

当设备旋转时,UIView 会正确更新。我正在计算它们的框架尺寸和来源。但是,标签不会保持居中,并且不遵守 Storyboard 中定义的约束。

以下是显示问题的屏幕截图。如果我注释掉 viewDidLayoutSubviews 方法,标签将完全居中(但是 UIView 的大小不正确)。我意识到我可以手动调整每个标签的框架,但我正在寻找一种方法让它们在新调整大小的 super View 中尊重它们的约束。 这是代码:

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic) CGFloat spacer;
@end

@implementation ViewController

@synthesize topLeftView, bottomRightView, topLeftLabel, bottomRightLabel;

- (void)viewDidLoad {
    [super viewDidLoad];

    topLeftLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    bottomRightLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

    self.spacer = 8.0f;
}

- (void)viewDidLayoutSubviews
{
    if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) {
        [self setupTopLeftForLandscape];
        [self setupBottomRightForLandscape];
    } else {
        [self setupTopLeftForPortrait];
        [self setupBottomRightForPortrait];
    }

}

- (void) setupTopLeftForPortrait {
    CGRect frame = topLeftView.frame;
    frame.origin.x = self.spacer;
    frame.origin.y = self.spacer;
    frame.size.width = self.view.frame.size.width - 2*self.spacer;
    frame.size.height = (self.view.frame.size.height - 3*self.spacer) * 0.5;
    [topLeftView setFrame:frame];
}

- (void) setupBottomRightForPortrait {
    CGRect frame = bottomRightView.frame;
    frame.origin.x = self.spacer;
    frame.origin.y = topLeftView.frame.size.height + 2*self.spacer;
    frame.size.width = topLeftView.frame.size.width;
    frame.size.height = topLeftView.frame.size.height;
    [bottomRightView setFrame:frame];
}

- (void) setupTopLeftForLandscape {
    CGRect frame = topLeftView.frame;
    frame.origin.x = self.spacer;
    frame.origin.y = self.spacer;
    frame.size.width = (self.view.frame.size.width - 3*self.spacer) * 0.5;
    frame.size.height = self.view.frame.size.height - 2*self.spacer;
    [topLeftView setFrame:frame];
}

- (void) setupBottomRightForLandscape {
    CGRect frame = bottomRightView.frame;
    frame.origin.x = self.topLeftView.frame.size.width + 2*self.spacer;
    frame.origin.y = self.spacer;
    frame.size.width = topLeftView.frame.size.width;
    frame.size.height = topLeftView.frame.size.height;
    [bottomRightView setFrame:frame];
}

@end

最佳答案

通常将框架与自动布局混合使用是个坏主意。 (异常(exception)情况是 View 层次结构使用包含不包含 View 的约束,然后从该点开始不使用任何约束[和其他警告])。一个大问题是约束系统通常不会从 setFrame 中获取任何信息。

另一个经验法则是,setFrame 和传统的布局树是在约束系统之前计算的。对于第一部分,这似乎违反直觉,但请记住 1) 在传统的布局树中, View 布局它们的 subview ,然后在它们上调用 layoutSubviews,所以每个人的父 View 框架在它自己布局之前设置,但 2) 在约束系统,它尝试从 subview 计算父 View 框架,自下而上。但是在自下而上获取信息后,每个 subview 都会报告信息,布局工作是自上而下完成的。


修复

那会给你留下什么?你是对的,你需要以编程方式设置它。在 IB 中无法指示您应该从上下切换到左右。以下是您可以如何做到这一点:

  1. 选择一个轮换并确保所有约束都已设置 您在界面生成器中想要的方式-例如,每种颜色 View 从 super View 中放置 8 个点(您的间隔 View )。 “明确约束”和 底部的“更新帧”按钮会帮助你,你会想要点击 经常这样做是为了确保它是同步的。
  2. 非常重要的是,左上角的 View 只能连接到 左(领先)和顶部以及右下角的 super View 仅由右侧(尾部)和底部连接。如果你清除 尺寸设置固定的高度和宽度,这将产生一个 警告。这是正常的,这种情况可以通过设置来解决 “等宽”和“等高”以及必要时第 3 步的一部分。 (请注意,要使值真正相等,常数必须为零。) 在其他情况下,我们必须放置一个约束并将其标记为“占位符” 使编译器静音,如果我们确定我们将填充信息但编译器不知道。
  3. 识别(或创建)链接右侧/底部的两个约束 查看左侧和顶部的某物。您可能想使用 IB 左侧的对象浏览器。在中创建两个导出 viewController.h 使用助理编辑器。看起来像:

    @property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomViewToTopConstraint; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *rightViewToLeftConstraint;

  4. 在 viewController 中实现 updateConstraints。这里是 逻辑会去:

.

-(void)updateViewConstraints 
{

//first remove the constraints

[self.view removeConstraints:@[self.rightViewToLeftConstraint, self.bottomViewToTopConstraint]];

  if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) {

    //align the tops equal
    self.bottomViewToTopConstraint = [NSLayoutConstraint constraintWithItem:self.bottomRightView
                                                                  attribute:NSLayoutAttributeTop
                                                                  relatedBy:NSLayoutRelationEqual
                                                                     toItem:self.topLeftView
                                                                  attribute:NSLayoutAttributeTop
                                                                 multiplier:1.0
                                                                   constant:0];
    //align to the trailing edge by spacer
    self.rightViewToLeftConstraint = [NSLayoutConstraint constraintWithItem:self.bottomRightView
                                                                  attribute:NSLayoutAttributeLeading
                                                                  relatedBy:NSLayoutRelationEqual
                                                                     toItem:self.topLeftView
                                                                  attribute:NSLayoutAttributeTrailing
                                                                 multiplier:1.0
                                                                   constant:self.spacer];
} else { //portrait

    //right view atached vertically to the bottom of topLeftView by spacer
    self.bottomViewToTopConstraint = [NSLayoutConstraint constraintWithItem:self.bottomRightView
                                                                  attribute:NSLayoutAttributeTop
                                                                  relatedBy:NSLayoutRelationEqual
                                                                     toItem:self.topLeftView
                                                                  attribute:NSLayoutAttributeBottom
                                                                 multiplier:1.0
                                                                   constant:self.spacer];

    //bottom view left edge aligned to left edge of top view 
    self.rightViewToLeftConstraint = [NSLayoutConstraint constraintWithItem:self.bottomRightView
                                                                  attribute:NSLayoutAttributeLeading
                                                                  relatedBy:NSLayoutRelationEqual
                                                                     toItem:self.topLeftView
                                                                  attribute:NSLayoutAttributeLeading
                                                                 multiplier:1.0
                                                                   constant:0];
}

[self.view addConstraints:@[self.rightViewToLeftConstraint, self.bottomViewToTopConstraint]];

[super updateViewConstraints];

由于添加约束后无法更改约束(常量除外),因此我们必须执行此删除-添加步骤。请注意 IB 中的那些也可能是占位符,因为我们每次都会删除它们(我们可以先检查)。我们可以将常量修改为某个偏移值,例如通过 spacer + topViewHight + spacer 与 superview 相关。但这意味着当自动布局去计算这个 View 时,你已经根据一些其他信息做出了假设,这些信息可能已经改变了。交换观点并改变它们相关的因素,这些因素旨在改变彼此相关的因素。

注意,因为Auto Layout在向上传递信息的时候会用到这里的约束,所以我们先修改它们,然后调用super。这是调用父类(super class)私有(private)实现来为这个 View 做计算,而不是 View 层次结构中这个 View 的父 View ,尽管实际上下一步将在树的更上层。

关于ios - 父 View 框架尺寸更改后更新约束,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28287013/

有关ios - 父 View 框架尺寸更改后更新约束的更多相关文章

  1. ruby-on-rails - Ruby on Rails 迁移,将表更改为 MyISAM - 2

    如何正确创建Rails迁移,以便将表更改为MySQL中的MyISAM?目前是InnoDB。运行原始执行语句会更改表,但它不会更新db/schema.rb,因此当在测试环境中重新创建表时,它会返回到InnoDB并且我的全文搜索失败。我如何着手更改/添加迁移,以便将现有表修改为MyISAM并更新schema.rb,以便我的数据库和相应的测试数据库得到相应更新? 最佳答案 我没有找到执行此操作的好方法。您可以像有人建议的那样更改您的schema.rb,然后运行:rakedb:schema:load,但是,这将覆盖您的数据。我的做法是(假设

  2. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  3. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  4. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  5. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

    我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

  6. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  7. ruby - Capistrano 3 在任务中更改 ssh_options - 2

    我尝试使用不同的ssh_options在同一阶段运行capistranov.3任务。我的production.rb说:set:stage,:productionset:user,'deploy'set:ssh_options,{user:'deploy'}通过此配置,capistrano与用户deploy连接,这对于其余的任务是正确的。但是我需要将它连接到服务器中配置良好的an_other_user以完成一项特定任务。然后我的食谱说:...taskswithoriginaluser...task:my_task_with_an_other_userdoset:user,'an_othe

  8. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  9. ruby-on-rails - 如何在 Rails View 上显示错误消息? - 2

    我是rails的新手,想在form字段上应用验证。myviewsnew.html.erb.....模拟.rbclassSimulation{:in=>1..25,:message=>'Therowmustbebetween1and25'}end模拟Controller.rbclassSimulationsController我想检查模型类中row字段的整数范围,如果不在范围内则返回错误信息。我可以检查上面代码的范围,但无法返回错误消息提前致谢 最佳答案 关键是您使用的是模型表单,一种显示ActiveRecord模型实例属性的表单。c

  10. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

随机推荐