Search code examples
iosswiftautolayout

AutoLayout divide width of view programatically


I have been changing some code over from using frames to using auto layout and I've hit a small snag

for background I have been building a calendar, my issue is in the calendar's Day view which I have been trying to make so that it replicates apple's calendar.

This app has been sat in my hard drive since 2018 so is somewhat out of date, but in 2018 it did the following:

Displayed Events by creating an EventContainerView and placing it onto of a TableView (not as a cell).

events which existed at the same time would shorten their width to prevent the container views from overlapping.

this last part is where my problem lies.

I did this by taking the width of the tableView, dividing it by the number of overlapping events and then multiplying it by a shiftBy variable to shift the event to the right

for the life of me I cannot see a way to implement this with autoLayout.

before AutoLayout

func drawEvent(_ event:Event, overlaps:Int, shiftBy:Int) -> EventContainerView{
....
....


let eventWidth = (tableView.frame.width - 30) / CGFloat(overlaps)
        
        let shift = eventWidth * CGFloat(shiftBy)
        var frame: CGRect
     if(shiftBy > 0){
         frame = CGRect(x: CGFloat(30 + (5*shiftBy)) + shift, y: startPoint, width: eventWidth, height: endpoint)
         
     }else{
         frame = CGRect(x: CGFloat(30) + shift, y: startPoint, width: eventWidth, height: endpoint)
         
     }

After AutoLayout

func drawEvent(_ event:Event, overlaps:Int, shiftBy:Int) -> EventContainerView{
....
....

let eventView = EventContainerView(forEvent: event, today: self)
           eventView.translatesAutoresizingMaskIntoConstraints = false
 var left: NSLayoutConstraint = eventView.leftAnchor.constraint(equalTo: tableView.leftAnchor, constant: +20)
   
    if(shiftBy>0){
        left = ????
    }
    
    tableView.addSubview(eventView)
        let layout = [eventView.topAnchor.constraint(equalTo: tableView.topAnchor, constant: startPoint),
        left,
        eventView.heightAnchor.constraint(equalToConstant: endpoint),
        eventView.widthAnchor.constraint(equalTo: tableView.widthAnchor, multiplier: 1/CGFloat(overlaps), constant: -30)]
    NSLayoutConstraint.activate(layout)
        return eventView

Can anybody offer up any suggestions as to how I might implement this piece of code?

Thanks

EDIT

this is what I'm trying to achieve (I took this on macOS's calendar but it still clearly show's how I want calendar events to appear)

Overlapping Events are stacked next to each other

and this is what I have (The darker yellow between 15 and 16 is another event stacked underneath the "Test Everything" event. I need one of them to shift over (along with any other events that may or may not be added to the same time frame)

App's events stack on top of each other not next to


Solution

  • Many different ways to approach this, but to make it close to what you were doing...

    You can create equal width views that span a width, using a fixed spacing between them and a specific "padding" on each side, by:

    • constrain the Leading of the First view to the Leading of the superview + padding
    • constrain the Leading of each Successive view to the Trailing of the Previous view + spacing
    • constrain the Trailing of the Last view to the Trailing of the superview - padding

    Then, constrain the Width of each view equalTo the width of the previous view.

    Here's a quick example:

    class AndyViewController: UIViewController {
    
        let numEvents = 1
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            // a var to track the previously added view
            var prevView: UIView!
            
            var startPoint: CGFloat = 100
            
            // create a couple eventViews
            for i in 0..<numEvents {
                let thisView = eventView()
                view.addSubview(thisView)
                
                // set top constraint
                thisView.topAnchor.constraint(equalTo: view.topAnchor, constant: startPoint).isActive = true
                
                // let's pretend each view is 20-pts "later"
                startPoint += 20.0
                
                // for now, make them all 50-pts tall
                thisView.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
                
                // if it's the first view
                if i == 0 {
                    // constrain its leading to view leading + 30
                    thisView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30.0).isActive = true
                } else {
                    // make sure prevView has been set
                    guard let pv = prevView else {
                        fatalError("Did something wrong!!!")
                    }
                    // start this view at the right-end of the previous view + 5
                    thisView.leadingAnchor.constraint(equalTo: prevView.trailingAnchor, constant: 5.0).isActive = true
                    // make this view width equal to previous view width
                    thisView.widthAnchor.constraint(equalTo: pv.widthAnchor).isActive = true
                }
                
                // if it's the last view
                if i == numEvents - 1 {
                    // constrain its trailing to view trailing -30
                    thisView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true
                }
                
                // set prevView to thisView
                prevView = thisView
            }
            
        }
    
        func eventView() -> UIView {
            let v = UIView()
            v.backgroundColor = UIColor.yellow.withAlphaComponent(0.8)
            v.translatesAutoresizingMaskIntoConstraints = false
            return v
        }
        
    }
    

    The result, using 1, 2, 3 or 4 "events":

    enter image description here

    enter image description here

    enter image description here

    enter image description here

    You should be able to adapt that to your needs.