Skip to content

Incorrect positioning of calendar with AutoLayout #389

@Serproger

Description

@Serproger

The modern way to build app interfaces is Auto Layout based on building constraints connecting views. Let's suppose we have some simple screen with one label at the top and calendar that occupies the remaining part of screen. KVKCalendarView has here the red background.

import KVKCalendar
import EventKit
import Foundation

class ViewController: UIViewController {

	var events = [Event]()
	
	private var label = {
		let label = UILabel()
		label.numberOfLines = 0
		label.translatesAutoresizingMaskIntoConstraints = false
		label.text = "Label with a very very very very very very very very very very very very very very very very very very long text"
		return label
	}()
	
	private lazy var calendarView: KVKCalendarView = {
		var style = Style()
		style.defaultType = .week
		let calendar = KVKCalendarView(frame: CGRect.zero, style: style)
		calendar.dataSource = self
		calendar.backgroundColor = .red
		return calendar
	}()
	
	private var calendarContainerView = {
		let view = UIView()
		view.translatesAutoresizingMaskIntoConstraints = false
		return view
	}()
	
	override func viewDidLoad() {
		super.viewDidLoad()
		view.addSubview(label)
		view.addSubview(calendarContainerView)
		debugPrint(view.safeAreaInsets)
		label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
		label.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
		label.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
		calendarContainerView.topAnchor.constraint(equalTo: label.bottomAnchor).isActive = true
		calendarContainerView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
		calendarContainerView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
		calendarContainerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
		calendarContainerView.addSubview(calendarView)
		createEvents { (events) in
			self.events = events
			self.calendarView.reloadData()
		}
	}
		
	override func viewDidLayoutSubviews() {
		super.viewDidLayoutSubviews()
		calendarView.reloadFrame(calendarContainerView.frame)
	}
		
	func createEvents(completion: ([Event]) -> Void) {
		let formatter = DateFormatter()
		formatter.dateFormat = "dd.MM.yyyy HH:mm"
		
		var firstEvent = Event(ID: "1")
		firstEvent.start = formatter.date(from: "31.10.2024 10:30")!
		firstEvent.end = formatter.date(from: "31.10.2024 11:00")!
		firstEvent.color = Event.Color(.red)
		
		var secondEvent = Event(ID: "2")
		secondEvent.start = formatter.date(from: "01.11.2024 14:00")!
		secondEvent.end = formatter.date(from: "01.11.2024 15:30")!
		secondEvent.color = Event.Color(.green)

		completion([
			firstEvent,
			secondEvent
		])
	}
}

extension ViewController: CalendarDataSource {
	func eventsForCalendar(systemEvents: [EKEvent]) -> [KVKCalendar.Event] {
		return events
	}
}

This screen is displayed so (obviously it's incorrect):
image

If we move reloadFrame call to viewWillLayoutSubviews as written in docs, we'll get this:
image

Similar bugs can be reproduced even without Auto layout, it's enough to pass to reloadFrame any frame with origin different from zero (yes, it can happen, when there are another views in the controller and calendar isn't at the top).

Please elaborate how can we achieve the correct positioning without big empty spaces?

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions