I'm trying to imitate the controller push transition but for views. But it loses constrains the moment I add the constrains of the new view even before the animation occurs. For a reproduction of issue scroll to the gif at the bottom.
Here's a snippet of the relevant prosperities: (the rest is merely a bunch of labels and buttons)
//MARK: Properities
var credential: AuthCredential!
let user = Auth.auth().currentUser
var userAuthenticated = false
let controllerTitle = "Update Email"
private var timer: Timer?
lazy var credentialVerificationView: UIView = {
let centerView = UIView()
centerView.frame.size.height = self.view.frame.height
centerView.frame.size.width = self.view.frame.width
centerView.backgroundColor = UIColor(named: "blankSlate")
return centerView
}()
lazy var emailUpdateView: UIView = {
let centerView = UIView()
centerView.frame.size.height = self.view.frame.height
centerView.frame.size.width = self.view.frame.width
centerView.backgroundColor = UIColor.red
return centerView
}()
I'm using transform to move the current view from the middle of the screen to -view.frame.width and the second view from -view.frame.width to the middle of the screen. func is:
private func startAutoScrollCardsCollectionView() {
/// This method start timer and fire it immediately
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [unowned self] _ in
UIView.animate(withDuration: 1) {
self.credentialVerificationView.transform = CGAffineTransform(translationX: -view.frame.width, y: 0)
self.emailUpdateView.transform = CGAffineTransform(translationX: -view.frame.width, y: 0)
}
}
}
And this is the ugly bit:
//MARK: authenticateUserAndConfigureView
/// the purpose of this func is merely sentimental; as long as its there it stops me from ever writing UI ever again
func authenticateUserAndConfigureView() {
// title = controllerTitle
view.backgroundColor = UIColor(named: "blankSlate")
emailTextField.textContentType = .username
emailTextField.keyboardType = .emailAddress
emailTextField.autocorrectionType = .no
emailTextField.autocapitalizationType = .none
emailTextField.spellCheckingType = .no
print("userAuthenticated = \(userAuthenticated)")
if userAuthenticated == true {
loginToContinueLabel.text = "Update Email"
infoLabel.text = currentEmailLabel1.text
errorLabel.text?.removeAll()
startAutoScrollCardsCollectionView()
emailUpdateView.addSubview(loginToContinueLabel)
loginToContinueLabel.anchor(top: emailUpdateView.topAnchor,
left: nil, bottom: nil,
right: nil,
paddingTop: 200,
paddingLeft: 0,
paddingBottom: 0,
paddingRight: 0,
width: 300, height: 50)
loginToContinueLabel.centerXAnchor.constraint(equalTo: emailUpdateView.centerXAnchor).isActive = true
emailUpdateView.addSubview(infoLabel)
infoLabel.anchor(top: loginToContinueLabel.bottomAnchor,
left: emailUpdateView.leftAnchor,
bottom: nil,
right: emailUpdateView.rightAnchor,
paddingTop: 24,
paddingLeft: 32,
paddingBottom: 0,
paddingRight: 32,
width: 0, height: 50)
infoLabel.centerXAnchor.constraint(equalTo: emailUpdateView.centerXAnchor).isActive = true
emailUpdateView.addSubview(emailContainerView)
emailContainerView.anchor(top: infoLabel.bottomAnchor,
left: emailUpdateView.leftAnchor,
bottom: nil,
right: emailUpdateView.rightAnchor,
paddingTop: 15,
paddingLeft: 32,
paddingBottom: 0,
paddingRight: 32,
width: 0, height: 50)
emailUpdateView.centerXAnchor.constraint(equalTo: emailUpdateView.centerXAnchor).isActive = true
emailUpdateView.addSubview(verifyLabel)
verifyLabel.anchor(top: emailContainerView.bottomAnchor,
left: emailUpdateView.leftAnchor,
bottom: nil,
right: emailUpdateView.rightAnchor,
paddingTop: 2,
paddingLeft: 32,
paddingBottom: 0,
paddingRight: 32,
width: 0, height: 20)
emailUpdateView.centerXAnchor.constraint(equalTo: emailUpdateView.centerXAnchor).isActive = true
emailUpdateView.addSubview(errorLabel)
errorLabel.anchor(top: verifyLabel.bottomAnchor,
left: nil,
bottom: nil,
right: nil,
paddingTop: 1,
paddingLeft: 0,
paddingBottom: 0,
paddingRight: 0,
width: 0, height: 0)
errorLabel.centerXAnchor.constraint(equalTo: emailUpdateView.centerXAnchor).isActive = true
emailUpdateView.addSubview(updateEmailButton)
updateEmailButton.anchor(top: errorLabel.bottomAnchor,
left: emailUpdateView.leftAnchor,
bottom: nil,
right: emailUpdateView.rightAnchor,
paddingTop: 16,
paddingLeft: 32,
paddingBottom: 0,
paddingRight: 32,
width: 0, height: 50)
updateEmailButton.centerXAnchor.constraint(equalTo: emailUpdateView.centerXAnchor).isActive = true
view.addSubview(emailUpdateView)
emailUpdateView.anchor(top: nil,
left: view.leftAnchor,
bottom: nil,
right: view.rightAnchor,
paddingTop: 0,
paddingLeft: +view.frame.width,
paddingBottom: 0,
paddingRight: -view.frame.width,
width: view.frame.width,
height: 0)
} else {
credentialVerificationView.addSubview(loginToContinueLabel)
loginToContinueLabel.anchor(top: credentialVerificationView.topAnchor,
left: nil, bottom: nil,
right: nil,
paddingTop: 200,
paddingLeft: 0,
paddingBottom: 0,
paddingRight: 0,
width: 300, height: 50)
loginToContinueLabel.centerXAnchor.constraint(equalTo: credentialVerificationView.centerXAnchor).isActive = true
credentialVerificationView.addSubview(infoLabel)
infoLabel.anchor(top: loginToContinueLabel.bottomAnchor,
left: credentialVerificationView.leftAnchor,
bottom: nil,
right: credentialVerificationView.rightAnchor,
paddingTop: 24,
paddingLeft: 32,
paddingBottom: 0,
paddingRight: 32,
width: 0, height: 50)
infoLabel.centerXAnchor.constraint(equalTo: credentialVerificationView.centerXAnchor).isActive = true
credentialVerificationView.addSubview(passwordContainerView)
passwordContainerView.anchor(top: infoLabel.bottomAnchor,
left: credentialVerificationView.leftAnchor,
bottom: nil,
right: credentialVerificationView.rightAnchor,
paddingTop: 16,
paddingLeft: 32,
paddingBottom: 0,
paddingRight: 32,
width: 0, height: 50)
passwordContainerView.centerXAnchor.constraint(equalTo: credentialVerificationView.centerXAnchor).isActive = true
credentialVerificationView.addSubview(loginButton)
loginButton.anchor(top: passwordContainerView.bottomAnchor,
left: credentialVerificationView.leftAnchor,
bottom: nil, right:
credentialVerificationView.rightAnchor,
paddingTop: 24,
paddingLeft: 32,
paddingBottom: 0,
paddingRight: 32,
width: 0, height: 50)
loginButton.centerXAnchor.constraint(equalTo: credentialVerificationView.centerXAnchor).isActive = true
credentialVerificationView.addSubview(needHelpButton)
needHelpButton.anchor(top: loginButton.bottomAnchor,
left: loginButton.leftAnchor,
bottom: nil,
right: nil,
paddingTop: 0,
paddingLeft: 0,
paddingBottom: 0,
paddingRight: 0,
width: 0, height: 0)
credentialVerificationView.addSubview(errorLabel)
errorLabel.anchor(top: needHelpButton.bottomAnchor,
left: credentialVerificationView.leftAnchor,
bottom: nil,
right: credentialVerificationView.rightAnchor,
paddingTop: 15,
paddingLeft: 32,
paddingBottom: 0,
paddingRight: 32,
width: 0, height: 0)
errorLabel.centerXAnchor.constraint(equalTo: credentialVerificationView.centerXAnchor).isActive = true
self.view.addSubview(credentialVerificationView)
credentialVerificationView.anchor(top: view.topAnchor,
left: view.leftAnchor,
bottom: view.bottomAnchor,
right: view.rightAnchor,
paddingTop: 0,
paddingLeft: 0,
paddingBottom: 0,
paddingRight: 0,
width: 0, height: 0)
credentialVerificationView.center = self.view.center
}
}
}
Related
Attempted to make a mock up of the apple maps UI but whenever I do I table view on top of my search input view the UI doesn't format correctly. when I comment the table view the UI works according and doesn't look a mess. I'm 100% sure its the table view but not sure why this would be the case. Does anyone have any apple maps mock ups code? - I also program without storyboards on UIKit
func configureViewComponents() {
backgroundColor = .white
addSubview(indicatorView)
indicatorView.anchor(top: topAnchor, left: nil, bottom: nil, right: nil, paddingTop: 8, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 40, height: 8)
indicatorView.centerX(inView: self)
configureSearchBar()
configureTableView()
configureGestureRecognizers()
}
func configureSearchBar() {
searchBar = UISearchBar()
searchBar.placeholder = "Search for a place or address"
searchBar.delegate = self
searchBar.barStyle = .black
searchBar.setBackgroundImage(UIImage(), for: .any, barMetrics: .default)
addSubview(searchBar)
searchBar.anchor(top: indicatorView.bottomAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, paddingTop: 4, paddingLeft: 8, paddingBottom: 0, paddingRight: 8, width: 0, height: 50)
}
func configureTableView() {
tableView = UITableView()
tableView.rowHeight = 72
tableView.delegate = self
tableView.dataSource = self
tableView.register(SearchCell.self, forCellReuseIdentifier: reuseIdentifier)
addSubview(tableView)
tableView.anchor(top: searchBar.bottomAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 8, paddingLeft: 0, paddingBottom: 100, paddingRight: 0, width: 0, height: 0)
}
Tableview Code
UI
UI without the Table view code
I have a UICollectionViewController header cell that displays information for a business.
A business may or may not have a description.
In my header cell I set a business object, and then set all the text for the labels etc. In here I also try calculating the required text field height based off of this post
What I need is to actually set my header cells height dynamically in the UICollectionViewController where it is used.
BusinessDetailHeader class:
var maxTextViewHeight: CGFloat = 0
var business: Business? {
didSet {
guard let imageUrl = business?.logoUrl else {return}
let url = URL(string: imageUrl)
logoView.kf.setImage(with: url)
headerImageView.kf.setImage(with: url)
guard let businessName = business?.name else {return}
guard let address = business?.address else {return}
guard let hoursOfOperation = business?.hoursOfOperation else {return}
guard let phoneNumber = business?.phoneNumber else {return}
businessNameLabel.text = businessName
addressLabel.text = address
hoursofOperationLabel.text = hoursOfOperation
phoneNumberLabel.text = phoneNumber
if let description = business?.description {
detailsTextField.text = description
let amountOfLinesToBeShown:CGFloat = 6
if let height = detailsTextField.font?.lineHeight {
maxTextViewHeight = height * amountOfLinesToBeShown
}
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
layoutViews()
}
fileprivate func layoutViews() {
addSubview(headerImageView)
headerImageView.anchor(top: topAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 130)
//ADJUST DETAIL HEIGHT HERE BASED ON CALCULATED HEIGHT FOR TEXTVIEW
addSubview(detailView)
detailView.anchor(top: headerImageView.bottomAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 200+maxTextViewHeight)
detailView.addSubview(businessNameLabel)
businessNameLabel.anchor(top: logoView.bottomAnchor, left: nil, bottom: nil, right: nil, paddingTop: 4, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
businessNameLabel.centerXAnchor.constraint(equalTo: detailView.centerXAnchor).isActive = true
detailView.addSubview(detailsTextField)
detailsTextField.anchor(top: businessNameLabel.bottomAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: maxTextViewHeight)
detailView.addSubview(addressLabel)
addressLabel.anchor(top: detailsTextField.bottomAnchor, left: nil, bottom: nil, right: nil, paddingTop: 4, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
addressLabel.centerXAnchor.constraint(equalTo: detailView.centerXAnchor).isActive = true
detailView.addSubview(hoursofOperationLabel)
hoursofOperationLabel.anchor(top: addressLabel.bottomAnchor, left: nil, bottom: nil, right: nil, paddingTop: 4, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
hoursofOperationLabel.centerXAnchor.constraint(equalTo: detailView.centerXAnchor).isActive = true
detailView.addSubview(phoneNumberLabel)
phoneNumberLabel.anchor(top: hoursofOperationLabel.bottomAnchor, left: nil, bottom: nil, right: nil, paddingTop: 4, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
phoneNumberLabel.centerXAnchor.constraint(equalTo: detailView.centerXAnchor).isActive = true
}
The height is updated properly with no issues but sometimes when the height is too big, the text and all the following elements overflow out of the header cell. I think this has to do with how I address the height of the cell in my UICollecitonViewController.
I set it's height like this:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: view.frame.width, height: 330)
}
The height of 330 here needs to be dynamic. Is this possible? Should I be setting the height property like this? I've seen posts like this one that try addressing this, but the solutions don't seem like they are the best.
Please check this link, it will be really helpful in how to make UIcollectionView header dynamic height?. Basically you need to set autolayout constraint according to your requirement; Need to call setNeedlayout() in referenceSizeForHeaderInSection (collectionViewFlowLayout delegate) method.
I have a function in my viewController that lays out my views using SnapKit. The funcion looks like this
#objc func setupView(){
//will add the scroll view to the subview
view.addSubview(scrollView)
scrollView.addSubview(imageContainer)
scrollView.addSubview(eventInfoVIew)
// scrollView.addSubview(currentEventImage)
scrollView.contentInsetAdjustmentBehavior = .never
scrollView.backgroundColor = .green
eventInfoVIew.backgroundColor = .red
imageContainer.backgroundColor = .blue
scrollView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
imageContainer.anchor(top: scrollView.topAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: view.frame.width, height: view.frame.height - 200)
eventInfoVIew.anchor(top: imageContainer.bottomAnchor, left: view.leftAnchor, bottom: scrollView.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.scrollIndicatorInsets = view.safeAreaInsets
scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: view.safeAreaInsets.bottom, right: 0)
}
However when I run the app one of the views isn't there and it makes no sense because to the best of my knowledge I laid out the constraints properly. The eventInfoView is the one giving me problems.
Sorry forgot to add image of current screen
Have you try debugging with the view inspector? What happens with your views? A screenshot of the view hierarchy would help a lot.
Without this, my hunch is that you are not using enough constraints and therefore your layout is ambiguous. Don't forget that UIScrollView requires 6 constraints to layout properly instead of 4.
Edit: If you want to check if the scrollView is the issue, try adding this code inside
scrollView.snp.makeConstraints { (make) in
make.edges.equalTo(view)
make.width.height.equalTo(view)
}
So I have a cell that is setup to have a collectionView inside of it. The height for that cell is given in this function.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath.section == 0 {
return CGSize(width: view.frame.width, height: 300)
}
return CGSize(width: view.frame.width, height: 280)
}
Inside the class for this cell I create a collectionView and again create a collection of cells inside of it.
#objc func setupViews(){
backgroundColor = .red
addSubview(categoryCollectionView)
addSubview(sectionNameLabel)
sectionNameLabel.anchor(top: topAnchor, left: leftAnchor, bottom: nil, right: nil, paddingTop: 0, paddingLeft: 14, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
categoryCollectionView.anchor(top: sectionNameLabel.bottomAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
categoryCollectionView.delegate = self
categoryCollectionView.dataSource = self
categoryCollectionView.showsHorizontalScrollIndicator = false
categoryCollectionView.register(CategoryEventCell.self, forCellWithReuseIdentifier: cellID)
}
Each cell has a background image and a title that is below the image. However the text is always somewhat cut off. My current constraints are here
#objc func setupViews(){
backgroundColor = .clear
setCellShadow()
addSubview(backgroundImageView)
addSubview(eventNameLabel)
backgroundImageView.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 9, paddingRight: 0, width: 0, height: 0)
eventNameLabel.anchor(top: backgroundImageView.bottomAnchor, left: leftAnchor, bottom: nil, right: nil, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
}
How would I change my constraints or text/element size to make sure everything fits?
My current view looks like so
You are pinning the image to the bottom of its cell, and the label has no say in how to fit in. Instead you should pin the image's to the label's top, and the label's bottom to the cell's bottom. Like this in the visual format language: [image]-[label]-|. You also need to edit the backgroundImageView's content compression resistance.
Here is the fixed version where the label causes the image's height to shrink upwards to fit in:
#objc func setupViews(){
backgroundColor = .clear
setCellShadow()
addSubview(backgroundImageView)
addSubview(eventNameLabel)
// ensure the image compresses, not the eventNameLabel, by lowering its priority
backgroundImageView.setContentCompressionResistancePriority(UILayoutPriority(600), for: .vertical)
backgroundImageView.anchor(top: topAnchor, left: leftAnchor, bottom: eventNameLabel.topAnchor, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
eventNameLabel.anchor(top: backgroundImageView.bottomAnchor, left: leftAnchor, bottom: bottomAnchor, right: nil, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
}
I have an App, that doesn't have a UINavigationController and it has a UICollectionView with some cells as its feed.
To avoid the cells appearing behind the status bar, I added a UIView behind it.
Before iPhone X it was pretty simple to do this, as the following:
let v = UIView()
v.backgroundColor = .white
view.addSubview(v)
v.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 0, paddinfLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 20)
By doing so, a tiny white layer would appear behind it as expected.
(this anchor method is from an extension, not language-default).
How can I achieve this for iPhone X ?
So far I tried the following code:
if #available(iOS 11, *) {
v.anchor(top: view.safeAreaLayoutGuide.topAnchor, left: view.safeAreaLayoutGuide.leftAnchor, bottom: nil, right: view.safeAreaLayoutGuide.rightAnchor, paddingTop: 0, paddinfLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 20)
let window = UIWindow(frame: UIScreen.main.bounds)
print("window.safeAreaInsets.top:", window.safeAreaInsets.top)
if window.safeAreaInsets.top > CGFloat(0.0) {
print("iPhone X")
v.anchor(top: view.safeAreaLayoutGuide.topAnchor, left: view.safeAreaLayoutGuide.leftAnchor, bottom: nil, right: view.safeAreaLayoutGuide.rightAnchor, paddingTop:-44, paddinfLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: window.safeAreaInsets.top)
} else {
v.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 0, paddinfLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 20)
}
}
but it doesn't work.
What's the best approach in this case?
Thank you
You have to tell your collectionView to adjust the insets:
if #available(iOS 11, *) {
collectionView.contentInsetAdjustmentBehavior = .scrollableAxes
}
This will make sure your content will be inset enough so it's not overlapped by the iPhone X's status bar.