似乎使用Xcode 8,在viewDidLoad
上,所有viewcontroller子视图都具有相同的1000x1000大小。奇怪的是,但是没关系,viewDidLoad
从来就不是正确调整视图大小的好地方。
但viewDidLayoutSubviews
是!
在我目前的项目中,我尝试打印按钮的大小:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
NSLog(@"%@", self.myButton);
}
日志显示myButton的大小为(1000x1000)!然后,如果我登录按钮单击,例如,日志显示正常大小。
我正在使用自动布局。
这是一个错误吗?
答案 0 :(得分:95)
现在,Interface Builder允许用户动态更改故事板中每个视图控制器的大小,以模拟某个设备的大小。
在此功能之前,用户应手动设置每个视图控制器大小。因此视图控制器以一定的大小保存,在initWithCoder
中用于设置初始帧。
现在,似乎initWithCoder
不使用故事板中定义的大小,并为viewcontroller视图定义了1000x1000像素大小&所有的子视图。
这不是问题,因为视图应始终使用以下任一布局解决方案:
自动布局,所有约束都会正确布局您的观点
autoresizingMask,它将布置没有附加任何约束的每个视图( note autolayout和margin constraints现在在同一视图中兼容\ o /!)
但 是所有与视图层相关的布局内容的问题,例如cornerRadius
,因为 autolayout 和 autoresizing mask 适用于图层属性。
要回答此问题,常见的方法是使用viewDidLayoutSubviews
(如果您在控制器中),或layoutSubview
(如果您在视图中)。此时(不要忘记调用他们的super
相对方法),你很确定所有的布局都已经完成了!
相当肯定吗?嗯...不完全,我已经说过,这就是我问这个问题的原因,在某些情况下,这个方法的视图仍然有1000x1000的大小。我认为我自己的问题没有答案。要提供有关它的最大信息:
1-只有在铺设细胞时才会发生!在UITableViewCell
&在子视图正确布局后,UICollectionViewCell
子类layoutSubview
将不会被调用。
2-正如@EugenDimboiu所说的那样(如果对你有用,请选择他的答案),在未布局的子视图上调用[myView layoutIfNeeded]
将及时正确布局。
- (void)layoutSubviews {
[super layoutSubviews];
NSLog (self.myLabel); // 1000x1000 size
[self.myLabel layoutIfNeeded];
NSLog (self.myLabel); // normal size
}
3-我认为,这绝对是一个错误。我已将它提交给雷达(id 28562874)。
PS:我不是英国人,如果我的语法应该纠正,请随时编辑我的帖子;)
PS2:如果你有更好的解决方案,请随意不要写另一个答案。我会移动接受的答案。
答案 1 :(得分:39)
您是否使用圆角作为按钮?
请先尝试拨打layoutIfNeeded()
。
答案 2 :(得分:18)
我知道这不是你的确切问题,但我遇到了类似的问题,尽管在viewDidLayoutSubviews中有一个正确的帧大小,我的一些观点被搞砸了。根据iOS 10发行说明:
“发送layoutIfNeeded到视图不会移动视图, 但在早期版本中,如果视图有 translatesAutoresizingMaskIntoConstraints设置为NO,如果是的话 通过约束定位,layoutIfNeeded将视图移动到 在将布局发送到子树之前匹配布局引擎。这些 更改纠正了这种行为,以及接收者的位置和通常 它的大小不受layoutIfNeeded的影响。
某些现有代码可能依赖于这种不正确的行为 现在纠正了。之前链接的二进制文件没有行为更改 iOS 10,但在iOS 10上构建时,您可能需要更正一些 通过发送-layoutIfNeeded到的超级视图的情况 translatesAutoresizingMaskIntoConstraints视图是前一个 接收器,或者在之前(或之后)定位和调整大小 取决于你想要的行为)layoutIfNeeded。
使用自动布局的自定义UIView子类的第三方应用程序 在调用super之前覆盖layoutSubviews和self上的脏布局 有可能在重建时触发布局反馈循环 iOS 10.当正确发送后续layoutSubviews调用时 他们必须确保在某些时候停止弄脏自己的布局(注意 在iOS 10之前的版本中跳过了这个调用。“
如果使用translatesAutoresizingMaskIntoConstraints,基本上你不能在View的子对象上调用layoutIfNeeded - 现在调用layoutIfNeeded必须在superView上,你仍然可以在viewDidLayoutSubviews中调用它。
答案 3 :(得分:18)
解决方案:将viewDidLayoutSubviews
内的所有内容包裹在DispatchQueue.main.async
中。
// swift 3
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
DispatchQueue.main.async {
// do stuff here
}
}
答案 4 :(得分:3)
这为我解决了(荒谬烦人的)问题:
- (void) viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.view.frame = CGRectMake(0,0,[[UIScreen mainScreen] bounds].size.width,[[UIScreen mainScreen] bounds].size.height);
}
编辑/注意:这是一个全屏ViewController。
答案 5 :(得分:3)
如果layoutSubViews(它们不是)中的帧不正确,您可以在主线程上分配异步代码。这为系统提供了一些时间来进行布局。执行发送的块时,帧的大小合适。
答案 6 :(得分:2)
实际上viewDidLayoutSubviews
也不是设置视图框架的最佳位置。据我所知,从现在开始,唯一应该在实际视图代码中使用layoutSubviews
方法。我希望我不对,如果不是真的,请有人纠正我!
答案 7 :(得分:0)
I already reported this issue to apple, this issue exist since a long time, when you are initializing UIViewController from Xib, but i found quite nice workaround. In addition to that i found that issue in some cases when layoutIfNeeded on UICollectionView and UITableView when datasource is not set in initial moment, and needed also swizzle it.
extension UIViewController {
open override class func initialize() {
if self !== UIViewController.self {
return
}
DispatchQueue.once(token: "io.inspace.uiviewcontroller.swizzle") {
ins_applyFixToViewFrameWhenLoadingFromNib()
}
}
@objc func ins_setView(view: UIView!) {
// View is loaded from xib file
if nibBundle != nil && storyboard == nil && !view.frame.equalTo(UIScreen.main.bounds) {
view.frame = UIScreen.main.bounds
view.layoutIfNeeded()
}
ins_setView(view: view)
}
private class func ins_applyFixToViewFrameWhenLoadingFromNib() {
UIViewController.swizzle(originalSelector: #selector(setter: UIViewController.view),
with: #selector(UIViewController.ins_setView(view:)))
UICollectionView.swizzle(originalSelector: #selector(UICollectionView.layoutSubviews),
with: #selector(UICollectionView.ins_layoutSubviews))
UITableView.swizzle(originalSelector: #selector(UITableView.layoutSubviews),
with: #selector(UITableView.ins_layoutSubviews))
}
}
extension UITableView {
@objc fileprivate func ins_layoutSubviews() {
if dataSource == nil {
super.layoutSubviews()
} else {
ins_layoutSubviews()
}
}
}
extension UICollectionView {
@objc fileprivate func ins_layoutSubviews() {
if dataSource == nil {
super.layoutSubviews()
} else {
ins_layoutSubviews()
}
}
}
Dispatch once extension:
extension DispatchQueue {
private static var _onceTracker = [String]()
/**
Executes a block of code, associated with a unique token, only once. The code is thread safe and will
only execute the code once even in the presence of multithreaded calls.
- parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
- parameter block: Block to execute once
*/
public class func once(token: String, block: (Void) -> Void) {
objc_sync_enter(self); defer { objc_sync_exit(self) }
if _onceTracker.contains(token) {
return
}
_onceTracker.append(token)
block()
}
}
Swizzle extension:
extension NSObject {
@discardableResult
class func swizzle(originalSelector: Selector, with selector: Selector) -> Bool {
var originalMethod: Method?
var swizzledMethod: Method?
originalMethod = class_getInstanceMethod(self, originalSelector)
swizzledMethod = class_getInstanceMethod(self, selector)
if originalMethod != nil && swizzledMethod != nil {
method_exchangeImplementations(originalMethod!, swizzledMethod!)
return true
}
return false
}
}
答案 8 :(得分:0)
我的问题是通过改变
的用法来解决的-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
self.viewLoginMailTop.constant = -self.viewLoginMail.bounds.size.height;
}
到
-(void)viewWillLayoutSubviews{
[super viewWillLayoutSubviews];
self.viewLoginMailTop.constant = -self.viewLoginMail.bounds.size.height;
}
所以,从Did到Will
超级奇怪
答案 9 :(得分:0)
更好的解决方案。
protocol LayoutComplementProtocol {
func didLayoutSubviews(with targetView_: UIView)
}
private class LayoutCaptureView: UIView {
var targetView: UIView!
var layoutComplements: [LayoutComplementProtocol] = []
override func layoutSubviews() {
super.layoutSubviews()
for layoutComplement in self.layoutComplements {
layoutComplement.didLayoutSubviews(with: self.targetView)
}
}
}
extension UIView {
func add(layoutComplement layoutComplement_: LayoutComplementProtocol) {
func findLayoutCapture() -> LayoutCaptureView {
for subView in self.subviews {
if subView is LayoutCaptureView {
return subView as? LayoutCaptureView
}
}
let layoutCapture = LayoutCaptureView(frame: CGRect(x: -100, y: -100, width: 10, height: 10)) // not want to show, want to have size
layoutCapture.targetView = self
self.addSubview(layoutCapture)
return layoutCapture
}
let layoutCapture = findLayoutCapture()
layoutCapture.layoutComplements.append(layoutComplement_)
}
}
使用
class CircleShapeComplement: LayoutComplementProtocol {
func didLayoutSubviews(with targetView_: UIView) {
targetView_.layer.cornerRadius = targetView_.frame.size.height / 2
}
}
myButton.add(layoutComplement: CircleShapeComplement())
答案 10 :(得分:0)
覆盖layoutSublayers(图层:CALayer),而不是单元子视图中的layoutSubviews,以拥有正确的框架
答案 11 :(得分:0)
如果您需要根据视图的框架进行操作-覆盖layoutSubviews并调用layoutIfNeeded
override func layoutSubviews() {
super.layoutSubviews()
yourView.layoutIfNeeded()
setGradientForYourView()
}
我遇到了一个问题, viewDidLayoutSubviews返回了错误的帧,我需要为其添加渐变。而且只有layoutIfNeeded做正确的事情:)
答案 12 :(得分:-1)