Home > Software design >  does anybody understand this paradox with swift frames?
does anybody understand this paradox with swift frames?

Time:12-09

In UIKIT I have two uiview's main view and uiview installed with storyboard at top with high in 1/3 of main View.

import UIKit

class ViewController: UIViewController {

@IBOutlet weak var TopView: UIView!
@IBOutlet weak var MiddleView: UIView!
@IBOutlet weak var BottomView: UIView!



override func viewDidLoad() {
    super.viewDidLoad()

    let t = Vvp(inView: TopView)
    TopView.addSubview(t)
    
    let bezierPath = UIBezierPath()
    bezierPath.move(to: CGPoint(x: 0, y: 0))
    bezierPath.addLine(to: CGPoint(x: TopView.frame.maxX, y: 0))
    
    bezierPath.close()

   
let shapeLayer = CAShapeLayer()

        shapeLayer.path = bezierPath.cgPath

        shapeLayer.strokeColor = UIColor.red.cgColor
        shapeLayer.fillColor = UIColor.red.cgColor
        shapeLayer.lineWidth = 1.0
    TopView.layer.addSublayer(shapeLayer)
    
}

}

second view:

func Vvp(inView: UIView)-> UIView {

let viewWithBeizer = UIView(frame: inView.frame)

    let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 0, y: 0))
bezierPath.addLine(to: CGPoint(x: inView.frame.maxX, y: 0))
    
    bezierPath.close()

   
let shapeLayer = CAShapeLayer()
        shapeLayer.path = bezierPath.cgPath

        // apply other properties related to the path
        shapeLayer.strokeColor = UIColor.blue.cgColor
        shapeLayer.fillColor = UIColor.blue.cgColor
        shapeLayer.lineWidth = 1.0
        viewWithBeizer.layer.addSublayer(shapeLayer)
        return viewWithBeizer

    

}

both views work with the same frame, at storyboard all borders are at zero why lines are not the same? phone view storybord view

CodePudding user response:

I think that this is a bug with iPod touch 7' emulator - with another emulators code works well. Below you can see the code from my question where I add 2px to red line.Iphone 11 pro

CodePudding user response:

The problem has nothing to do with where the lines are being drawn...

The issue is that you are referring to frame when you should be using bounds, and you're setting the frames before auto-layout has configured your views.

Based on your screen-shots, you are laying out your views in Storyboard based on an iPhone model with a Notch... so, in viewDidLoad() your TopView has the frame that was set in Storyboard.

This is how it looks using an iPhone 13 Pro in Storyboard:

enter image description here

As you can see, even though the yellow TopView is constrained to the top of the safe area, its Y position is 44. So, your code in your func Vvp(inView: UIView) is setting the Frame Y-position to 44, instead of Zero.

If you add these 4 lines at the end of viewDidLoad():

    TopView.layer.addSublayer(shapeLayer)

    // move t (from Vvp(inView: TopView))
    //  40-pts to the right
    t.frame.origin.x  = 40.0
    // give it an orange background color
    t.backgroundColor = .orange
    // allow it to show outside the bounds of TopView
    TopView.clipsToBounds = false
    // bring TopView to the front of the view hierarchy
    view.bringSubviewToFront(TopView)

The output on an iPad Touch 7th Gen looks like this:

enter image description here

as you can see, TopView's subview (the orange view) is much larger than TopView, and is showing up where you told it to: 44-pts from the top of TopView.

To use the code the way you've written it, you need to call that func - along with the shapeLayer code for TopView - later in the controller's lifecycle... such as in viewDidLayoutSubviews(). If you do that, though, you need to remember it will be called multiple times (any time the main view changes, such as on device rotation), so you'll want to make sure you don't repeatedly add new subviews and layers.

Here's a quick modification of your code:

class ViewController: UIViewController {
    
    @IBOutlet weak var TopView: UIView!
    @IBOutlet weak var MiddleView: UIView!
    @IBOutlet weak var BottomView: UIView!

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        if TopView.subviews.count == 0 {
            // we haven't added the subview or shape layer,
            //  so let's do that here
        
            let t = Vvp(inView: TopView)
            TopView.addSubview(t)
            
            let bezierPath = UIBezierPath()
            bezierPath.move(to: CGPoint(x: 0, y: 0))
            bezierPath.addLine(to: CGPoint(x: TopView.frame.maxX, y: 0))
            
            let shapeLayer = CAShapeLayer()
            
            shapeLayer.path = bezierPath.cgPath
            
            shapeLayer.strokeColor = UIColor.red.cgColor
            shapeLayer.fillColor = UIColor.red.cgColor
            shapeLayer.lineWidth = 1.0
            TopView.layer.addSublayer(shapeLayer)
        }

    }
    
    func Vvp(inView: UIView)-> UIView {
        
        let viewWithBeizer = UIView(frame: inView.bounds)
        
        let bezierPath = UIBezierPath()
        bezierPath.move(to: CGPoint(x: 0, y: 0))
        bezierPath.addLine(to: CGPoint(x: inView.bounds.maxX, y: 0))
        
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = bezierPath.cgPath
        
        // apply other properties related to the path
        shapeLayer.strokeColor = UIColor.blue.cgColor
        shapeLayer.fillColor = UIColor.blue.cgColor
        shapeLayer.lineWidth = 1.0
        viewWithBeizer.layer.addSublayer(shapeLayer)
        return viewWithBeizer
        
    }
    
}

Result (blue line is not visible, because we've added the red line on top of it):

enter image description here

A better approach, though, is to A) use auto-layout constraints, and B) handle your shapeLayer logic inside a custom UIView subclass -- but that's another topic.

  • Related