skip to Main Content

I want to my interface look the same on iPhone and iPad. Like on my images:

enter image description here

And I use UICollectionCompostionLayout and this code to do it:

func carouselSection(using section: Section) -> NSCollectionLayoutSection {
    
    let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
    let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
    
    var layoutGroupSize = NSCollectionLayoutSize(widthDimension: .absolute(0), heightDimension: .absolute(0))
    var layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: layoutGroupSize, subitems: [layoutItem])
    
    switch UIDevice.current.userInterfaceIdiom {
    case .phone:
        
        let a:CGFloat = UIScreen.main.bounds.size.height/8
        let b:CGFloat = UIScreen.main.bounds.size.height
        
        layoutGroupSize = NSCollectionLayoutSize(widthDimension: .absolute(b - (a) ), heightDimension: .absolute(b))
        layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: layoutGroupSize, subitems: [layoutItem])
        layoutGroup.contentInsets = NSDirectionalEdgeInsets(top: a, leading: a/2, bottom: a, trailing: a/2)
        
    case .pad:
        
        let a:CGFloat = UIScreen.main.bounds.size.height*0.25
        let h:CGFloat = a * 1.8
        let b:CGFloat = UIScreen.main.bounds.size.height
        
        layoutGroupSize = NSCollectionLayoutSize(widthDimension: .absolute(b - (h)), heightDimension: .absolute(b - (a)))
        layoutGroup = NSCollectionLayoutGroup.horizontal(layoutSize: layoutGroupSize, subitems: [layoutItem])
        layoutGroup.contentInsets = NSDirectionalEdgeInsets(top: a, leading: a/10, bottom: 0, trailing: a/10)
        
    default:
        
        print(UIScreen.main.bounds.size.height)
    }

    let layoutSection = NSCollectionLayoutSection(group: layoutGroup)
    layoutSection.orthogonalScrollingBehavior = .groupPagingCentered
    
    layoutSection.visibleItemsInvalidationHandler = { (items, offset, environment) in
        items.forEach { item in
            let distanceFromCenter = abs((item.frame.midX - offset.x) - environment.container.contentSize.width / 2.0)
            let minScale: CGFloat = 0.7
            let maxScale: CGFloat = 1.1
            let scale = max(maxScale - (distanceFromCenter / environment.container.contentSize.width), minScale)
            item.transform = CGAffineTransform(scaleX: scale, y: scale)
        }
    }
    
    return layoutSection
}

I get approximately the same result as I want. But I’m worried about my code. I do too many weird calculations to make my interface look the same on iPhone and iPad.

like this for iPhone:

let a:CGFloat = UIScreen.main.bounds.size.height/8
let b:CGFloat = UIScreen.main.bounds.size.height

like this on iPad:

let a:CGFloat = UIScreen.main.bounds.size.height*0.25
let h:CGFloat = a * 1.8
let b:CGFloat = UIScreen.main.bounds.size.height

Also I do different calculations for iPhone and iPad although UICollectionCompostionLayout was created specifically to do a minimum of calculations but the interfaces looked the same on different devices. What should I fix in my code?

2

Answers


  1. I think I got a solution which removes the funky calculations:

        func createLayout() -> UICollectionViewCompositionalLayout {
            UICollectionViewCompositionalLayout {
                (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
                
                let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
                let item = NSCollectionLayoutItem(layoutSize: itemSize)
    
                let interGroupSpacing = CGFloat(64)
                let width = layoutEnvironment.container.contentSize.width - interGroupSpacing * 2
                let groupWidth = width / 3
                
                let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(groupWidth), heightDimension: .absolute(groupWidth))
                let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
                
                let section = NSCollectionLayoutSection(group: group)
                let centerOffset = (layoutEnvironment.container.contentSize.height / 2) - (groupWidth / 2)
                section.contentInsets = NSDirectionalEdgeInsets(top: centerOffset, leading: 0, bottom: 0, trailing: 0)
                section.interGroupSpacing = interGroupSpacing * 1.5
                section.orthogonalScrollingBehavior = .groupPagingCentered
                section.contentInsetsReference = .none
                
                section.visibleItemsInvalidationHandler = { (items, offset, environment) in
                    items.forEach { item in
                        let distanceFromCenter = abs((item.frame.midX - offset.x) - environment.container.contentSize.width / 2.0)
                        let minScale: CGFloat = 0.8
                        let maxScale: CGFloat = 1.1
                        let scale = max(maxScale - (distanceFromCenter / environment.container.contentSize.width), minScale)
                        item.transform = CGAffineTransform(scaleX: scale, y: scale)
                    }
                }
                return section
            }
        }
    

    My main idea was to take contentSize.width as a reference point, to calculate the groupWidth for 3 equally sized boxes,

    let interGroupSpacing = CGFloat(64)
    let width = layoutEnvironment.container.contentSize.width - interGroupSpacing * 2
    let groupWidth = width / 3
    

    We can then set the group size to this absolute value and calculate the section offset to center the groups vertically:

    let centerOffset = (layoutEnvironment.container.contentSize.height / 2) - (groupWidth / 2)
    section.contentInsets = NSDirectionalEdgeInsets(top: centerOffset, leading: 0, bottom: 0, trailing: 0)
    

    The final piece is to set section.interGroupSpacing to something larger than interGroupSpacing like interGroupSpacing * 1.5 to push the left and right group out of the container.

    I also had to adjust your scaling code slightly and add section.contentInsetsReference = .none because there were some issues on iPhone with elements appearing behind the notch.

    Result on iPhone 15

    Result on iPhone 15

    Result on iPad 10th gen

    Result on iPad 10th gen

    Login or Signup to reply.
  2. This is not direct answer for your question but a guide for UI how you can handle

    Below is what I use for sizing & even fonts. I use it everywhere…

    let screenSize : CGRect = UIScreen.main.bounds
    let screenWidth : CGFloat = screenSize.width
    let iPhoneFactorX : CGFloat = screenWidth/428.0
    

    Note, always depend on width as height can be different.

    Above 428 is nothing but the designer width of the screen (e.x for my portrait app, designer design width of screen as 428)

    Now you will give sizes based on the design you get.

    Let me explain with example.

    enter image description here

    In this UI I will explain with the Send OTP button. Code will be as below.

    sendButton.Top == mobileInputTF.Bottom + (36*iPhoneFactorX) // top constraint
    sendButton.Height == 48*iPhoneFactorX // height constraint
    sendButton.Leading == self.view.Leading + (43*iPhoneFactorX) // leading constraint
    sendButton.Trailing == self.view.Trailing - (43*iPhoneFactorX) // trailing constraint
    

    OR you can do as per below image

    sendButton.Top == mobileInputTF.Bottom + (36*iPhoneFactorX)
    sendButton.Height == 48*iPhoneFactorX
    sendButton.Width == 342*iPhoneFactorX // width constraint
    sendButton.CenterX == self.view.CenterX // to bring button in center
     
    

    enter image description here

    And don’t forget to make your collection view center vertically

    collectionView.centerVertically()
     
    

    Now if you run app in iPhone OR iPad, you will see same UI.

    Note : I am using Stevia library for UI purpose. Stevia & above iPhoneFactorX make great (pixel perfect) UI…

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search