无法满足嵌套堆栈视图的约束

时间:2020-02-26 11:56:17

标签: swift nslayoutconstraint uistackview

在尝试调试控制台中的// =========================== // ancillary geometric classes // =========================== var Point = function(x, y) { this.x = x; this.y = y; }; Point.prototype = { dist: function(p) { return this.vect(p).norm(); }, vect: function(p) { return new Point(p.x - this.x, p.y - this.y); }, norm: function(p) { return Math.sqrt(this.x * this.x + this.y * this.y); }, add: function(v) { return new Point(this.x + v.x, this.y + v.y); }, mult: function(a) { return new Point(this.x * a, this.y * a); } }; var Circle = function(radius, center) { this.r = radius; this.c = center; }; Circle.prototype = { surface: function() { return Math.PI * this.r * this.r; }, distance: function(circle) { return this.c.dist(circle.c) - this.r - circle.r; } }; // ========================= // circle packer lives here! // ========================= var Packer = function(circles, ratio) { this.circles = circles; this.ratio = ratio || 1; this.list = this.solve(); }; Packer.prototype = { // try to fit all circles into a rectangle of a given surface compute: function(surface) { // check if a circle is inside our rectangle function in_rect(radius, center) { if (center.x - radius < -w / 2) return false; if (center.x + radius > w / 2) return false; if (center.y - radius < -h / 2) return false; if (center.y + radius > h / 2) return false; return true; } // approximate a segment with an "infinite" radius circle function bounding_circle(x0, y0, x1, y1) { var xm = Math.abs((x1 - x0) * w); var ym = Math.abs((y1 - y0) * h); var m = xm > ym ? xm : ym; var theta = Math.asin(m / 4 / bounding_r); var r = bounding_r * Math.cos(theta); return new Circle( bounding_r, new Point( (r * (y0 - y1)) / 2 + ((x0 + x1) * w) / 4, (r * (x1 - x0)) / 2 + ((y0 + y1) * h) / 4 ) ); } // return the corner placements for two circles function corner(radius, c1, c2) { var u = c1.c.vect(c2.c); // c1 to c2 vector var A = u.norm(); if (A == 0) return []; // same centers u = u.mult(1 / A); // c1 to c2 unary vector // compute c1 and c2 intersection coordinates in (u,v) base var B = c1.r + radius; var C = c2.r + radius; if (A > B + C) return []; // too far apart var x = (A + (B * B - C * C) / A) / 2; var y = Math.sqrt(B * B - x * x); var base = c1.c.add(u.mult(x)); var res = []; var p1 = new Point(base.x - u.y * y, base.y + u.x * y); var p2 = new Point(base.x + u.y * y, base.y - u.x * y); if (in_rect(radius, p1)) res.push(new Circle(radius, p1)); if (in_rect(radius, p2)) res.push(new Circle(radius, p2)); return res; } ///////////////////////////////////////////////////////////////// // deduce starting dimensions from surface var bounding_r = Math.sqrt(surface) * 100; // "infinite" radius var w = (this.w = Math.sqrt(surface * this.ratio)); var h = (this.h = this.w / this.ratio); // place our bounding circles var placed = [ bounding_circle(1, 1, 1, -1), bounding_circle(1, -1, -1, -1), bounding_circle(-1, -1, -1, 1), bounding_circle(-1, 1, 1, 1) ]; // Initialize our rectangles list var unplaced = this.circles.slice(0); // clones the array while (unplaced.length > 0) { // compute all possible placements of the unplaced circles var lambda = {}; var circle = {}; for (var i = 0; i != unplaced.length; i++) { var lambda_min = 1e10; lambda[i] = -1e10; // match current circle against all possible pairs of placed circles for (var j = 0; j < placed.length; j++) for (var k = j + 1; k < placed.length; k++) { // find corner placement if (k > 3) { zog = 1; } var corners = corner(unplaced[i], placed[j], placed[k]); // check each placement for (var c = 0; c != corners.length; c++) { // check for overlap and compute min distance var d_min = 1e10; for (var l = 0; l != placed.length; l++) { // skip the two circles used for the placement if (l == j || l == k) continue; // compute distance from current circle var d = placed[l].distance(corners[c]); if (d < 0) break; // circles overlap if (d < d_min) d_min = d; } if (l == placed.length) { // no overlap if (d_min < lambda_min) { lambda_min = d_min; lambda[i] = 1 - d_min / unplaced[i]; circle[i] = corners[c]; } } } } } // select the circle with maximal gain var lambda_max = -1e10; var i_max = -1; for (var i = 0; i != unplaced.length; i++) { if (lambda[i] > lambda_max) { lambda_max = lambda[i]; i_max = i; } } // failure if no circle fits if (i_max == -1) break; // place the selected circle unplaced.splice(i_max, 1); placed.push(circle[i_max]); } // return all placed circles except the four bounding circles this.tmp_bounds = placed.splice(0, 4); return placed; }, // find the smallest rectangle to fit all circles solve: function() { // compute total surface of the circles var surface = 0; for (var i = 0; i != this.circles.length; i++) { surface += Math.PI * Math.pow(this.circles[i], 2); } // set a suitable precision var limit = surface / 1000; var step = surface / 2; var res = []; while (step > limit) { var placement = this.compute.call(this, surface); console.log( "placed", placement.length, "out of", this.circles.length, "for surface", surface ); if (placement.length != this.circles.length) { surface += step; } else { res = placement; this.bounds = this.tmp_bounds; surface -= step; } step /= 2; } return res; } }; // ==== // demo // ==== function draw_result(packer) { function draw_circle(circle) { ctx.beginPath(); ctx.arc( (circle.c.x + dx) * zoom + mx, (circle.c.y + dy) * zoom + my, circle.r * zoom, 0, 2 * Math.PI ); m = circle.r; //console.log("radius",m); ctx.closePath(); ctx.stroke(); } var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); canvas.width += 0; // clear canvas var margin_factor = 0.1; var mx = (canvas.width * margin_factor) / 2; var my = (canvas.height * margin_factor) / 2; var dx = packer.w / 2; var dy = packer.h / 2; var zx = (canvas.width * (1 - margin_factor)) / packer.w; var zy = (canvas.height * (1 - margin_factor)) / packer.h; var zoom = zx < zy ? zx : zy; // draw all circles ctx.strokeStyle = "black"; for (var i = 0; i != packer.list.length; i++) draw_circle(packer.list[i]); v = packer.list; console.log("circles", v); // draw bounding circles ctx.strokeStyle = "red"; for (var i = 0; i != packer.bounds.length; i++) draw_circle(packer.bounds[i]); // draw rectangle ctx.strokeStyle = "orange"; ctx.beginPath(); ctx.rect( (-packer.w / 2 + dx) * zoom + mx, (-packer.h / 2 + dy) * zoom + my, packer.w * zoom, packer.h * zoom ); let a = packer.w * zoom; let b = packer.h * zoom; console.log("width", a, "length", b); ctx.closePath(); ctx.stroke(); } function draw() { var circles = parseInt(document.getElementById("c").value); var ratio = parseFloat(document.getElementById("r").value); var min_r = parseInt(document.getElementById("a").value); var max_r = parseInt(document.getElementById("b").value); var radiuses = []; for (var i = 0; i != circles; i++) radiuses.push(Math.random() * (max_r - min_r) + min_r); var packer = new Packer(radiuses, ratio); draw_result(packer); } window.onload = draw; 警告时,我正变得疯狂起来,当我从iPhone肖像转换为iPhone横向时出现。我正在使用带有嵌套堆栈视图的Dashboard类型视图。如果宽度尺寸类是常规的,则顶部堆栈将轴更改为.horizo​​ntal。

视图层次结构:

  • 查看
    • 滚动视图
      • 基本视图
        • 仪表板堆栈视图
          • 顶部堆栈视图
            • 进度视图
            • 统计视图
          • 中间视图
          • 底视图

enter image description here

代码

  • 所有组件在Unable to satisfy constraints中都有translatesAutoresizingMaskIntoConstraints = false
  • 在检测到尺寸类别变化时对约束进行操作
viewDidLoad

调试器输出

当我将iPhone从纵向旋转到横向时,会收到此消息。

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {

    setupDashboardView()
}

func setupDashboardView() {
    let sizeClass = self.traitCollection

    if sizeClass.isIpad {
        topStack.axis = .horizontal
        topStackHeight.constant = (self.dashboardStack.bounds.height*0.3)
        progressViewWidth.constant = (self.dashboardStack.bounds.width*0.25)
        progressViewHeight.isActive = false
    } else if sizeClass.isIphonePortrait {
        topStack.axis = .vertical
        topStackHeight.constant = self.view.frame.width*1.5
        progressViewWidth.isActive = false
        progressViewHeight.isActive = true
        progressViewHeight.constant = self.view.frame.width
    } else if sizeClass.isIphoneLandscape {
        progressViewHeight.isActive = false
        topStack.axis = .horizontal
        topStackHeight.constant = self.view.frame.height*0.5
        progressViewWidth.isActive = true
        progressViewWidth.constant = self.view.frame.width*0.25
        }
        dashboardStack.spacing = 5
        topStack.spacing = 5

        self.updateViewConstraints()
        self.viewDidLayoutSubviews()
    }


extension UITraitCollection {
    var isIpad: Bool {
        return horizontalSizeClass == .regular && verticalSizeClass == .regular
    }
    var isIphoneLandscape: Bool {
        return verticalSizeClass == .compact
    }
    var isIphonePortrait: Bool {
        return horizontalSizeClass == .compact && verticalSizeClass == .regular
    }
    var isIphone: Bool {
        return isIphoneLandscape || isIphonePortrait
    }
}

//For constraint debugging
extension NSLayoutConstraint {
    override public var description: String {
        let id = identifier ?? ""
        return "id: \(id), constant: \(constant)"
    }
}

1 个答案:

答案 0 :(得分:0)

依赖于其他布局操作的约束可能会变得棘手。自动布局会在尝试满足所有约束条件时进行多次“通过”。

这在比例约束和可能受堆栈视图影响的约束中尤为明显(它们正在执行自己的布局操作)。

为避免警告,您通常需要调整约束优先级。

而且,在激活/取消激活约束时,您需要注意顺序。

我按照您显示的方式布置了对象,对于您的情况,您应该能够通过以下方式修复问题:

  • progressViewHeightprogressViewWidthtopStackHeight约束的优先级分别更改为999
  • 约束更改顺序应为
    • 首先在视图/标签/等上设置.isActive = false
    • 接下来要更改视图/标签/等上的.constant
    • 随后在视图/标签/等上设置.isActive = true
    • 随后通过更改堆栈视图上的.constant
    • 并最终更改堆栈视图的.axis

并非所有规则都是一成不变的规则,但是从经验来看,这是一个不错的选择。

尝试按上述约束条件更改优先级,并按如下所示更改setupDashboardView()函数:

func setupDashboardView() {
    let sizeClass = self.traitCollection

    if sizeClass.isIpad {
        progressViewHeight.isActive = false
        progressViewWidth.constant = (self.dashboardStack.bounds.width*0.25)
        topStackHeight.constant = (self.dashboardStack.bounds.height*0.3)
        topStack.axis = .horizontal
    } else if sizeClass.isIphonePortrait {
        progressViewWidth.isActive = false
        progressViewHeight.isActive = true
        progressViewHeight.constant = self.view.frame.width
        topStackHeight.constant = self.view.frame.width*1.5
        topStack.axis = .vertical
    } else if sizeClass.isIphoneLandscape {
        progressViewHeight.isActive = false
        progressViewWidth.constant = self.view.frame.width*0.25
        progressViewWidth.isActive = true
        topStackHeight.constant = self.view.frame.height*0.5
        topStack.axis = .horizontal
    }
    dashboardStack.spacing = 5
    topStack.spacing = 5

    // not needed
    //self.updateViewConstraints()

    // NEVER call this yourself
    //self.viewDidLayoutSubviews()
}