Monday, May 30, 2022

StackView - Constraining Contained Views

 These are the steps to layout a StackView's contained views by constraining the height of the contained views.


Step 1: Add Vertical Stack View


Step 2: Constrain the Stack View


Step 3: Add UIView to the Stack View


Step 4: Duplicate the UIView so that there are three UIViews


Step 5: Set the background color of each UIView to a unique color.


Step 6: Constrain the top UIView to the bottom UIView in the stack. Set to "Equal Heights"


Step 7: Constrain the middle UIView to the bottom UIView in the stack. Set to "Equal Heights"


Step 8: Change the middle UIView's constraint multiplier to 1.0



Step 9: Change the top UIView's constraint multiplier to 0.5

Sunday, May 29, 2022

UIViewExtended - StackView and FillProportionally

 Hav you have ever put a UIView in a StackView and wondered why "Fill Proportionally" wasn't working when you set the UIView's "Intrinsic Size"?

It's because the UIView doesn't let the intrinsic value be set, and therefore it can't be read.

So, I am thinking, there must be a way to add a property to the UIView via a subclass that will get the job done.

The approach is to use @IBInspectable.

Here is the source:


//

//  UIViewExtended.swift

//

//  Created by Geoffrey Slinker on 5/28/22.

//  Copyright © 2022 Geoffrey Slinker. All rights reserved.

//


import Foundation

import UIKit


class UIViewExtended : UIView {

    

    enum Keys: String {

      case proportionalSize = "ProportionalSize"

    }

    

    var proportionalSize : CGSize

    

    

    override init(frame: CGRect) {

        proportionalSize = CGSize(width:1.0, height:1.0)

        super.init(frame:frame)

    }

    

    required init?(coder aDecoder: NSCoder) {

        let tempSize : CGSize? = aDecoder.decodeCGSize(forKey: Keys.proportionalSize.rawValue) as? CGSize

        if tempSize != nil {

            proportionalSize = tempSize!

        }

        else {

            proportionalSize = CGSize(width:1.0, height:1.0)

        }

        super.init(coder: aDecoder)

    }

    

    override func encode(with coder: NSCoder) {

        coder.encode(self.proportionalSize, forKey: Keys.proportionalSize.rawValue)

        super.encode(with: coder)

    }

    

    @IBInspectable var intrinsicSize: CGSize {

        get {

            return proportionalSize

        }

        set {

            proportionalSize = newValue

        }

    }

    

    override open var intrinsicContentSize : CGSize {

        get {

            return proportionalSize

        }

    }

    

}


Just set the intrinsic size in IB (Interface Builder). If you want to have the simulated view in IB look the same, keep the custom intrinsic size the same as the intrinsic size placeholder values. Yes, you have to manually keep the values the same, but at least you can do it all in IB.




Set the values here:







If you want to UI to simulate the proportions then keep the custom value the same as the Intrinsic Size Placeholder value.




Here is the unit test. The unit test also gets the XML so that it can be examined in the debugger.


func testUIViewExtended() throws {

        let view : UIViewExtended = UIViewExtended.init(frame: CGRect(x: 0,y: 0, width: 100, height: 50))

        

        view.intrinsicSize = CGSize(width: 5,height: 5)

        

        

        //Look at the xml in the debugger

        let archiver = NSKeyedArchiver(requiringSecureCoding: false)

        archiver.outputFormat = .xml

        archiver.encodeRootObject(view)

        let someData = archiver.encodedData

        let stringData = String(decoding: someData, as: UTF8.self)


        

        let codedData = try! NSKeyedArchiver.archivedData(withRootObject: view,

                                                            requiringSecureCoding: false)

        

        

        let result = try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(codedData) as?

            UIViewExtended

        

        XCTAssert(result?.frame.width == 100)

        XCTAssert(result?.frame.height == 50)


        XCTAssert(result?.intrinsicSize.width == 5)

        XCTAssert(result?.intrinsicSize.height == 5)


    }


See also:


StackView - Constraining Contained Views