iosswiftnavigation

How to cover content with using navigation bar?


I want the navigation bar to overlap all the content in the app and I use this code to trying to do this:

SceneDelegate

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
                    
        if let windowScene = (scene as? UIWindowScene) {
            
            let window = UIWindow(windowScene: windowScene)
            
            let nav = UINavigationController()
            let mainView = StoreViewController()
            nav.viewControllers = [mainView]
            nav.navigationBar.backgroundColor = .green.withAlphaComponent(0.5)
            window.rootViewController = nav
            
            self.window = window
            window.makeKeyAndVisible()
            
        }
    }

}

StoreViewController

class StoreViewController: UIViewController {
    
    let backgroundView = BackgroundView()
    var collectionView: UICollectionView!
    var dataSource: UICollectionViewDiffableDataSource<Section, Item>?
    let sections = Bundle.main.decode([Section].self, from: "section.json")
    
    override func viewDidLoad() {
        super.viewDidLoad()
        createCollectionView()
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        collectionView.scrollToItem(at: IndexPath(item: 0, section: 0), at: .centeredHorizontally, animated: false)
    }
    
    func createDataSource() {
        
        dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { collectionView, indexPath, item in
            switch self.sections[indexPath.section].identifier {
                
            case "featuredCell": return self.configure(FeaturedCell.self, with: item, for: indexPath)
                
            default: return self.configure(CarouselCell.self, with: item, for: indexPath)
            }
        }
        
        dataSource?.supplementaryViewProvider = { [weak self] collectionView, kind, indexPath in
            guard let sectionHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: SectionHeader.reuseIdentifier, for: indexPath) as? SectionHeader
            else { return nil }
            guard let firstApp = self?.dataSource?.itemIdentifier(for: indexPath) else { return nil }
            guard let section = self?.dataSource?.snapshot().sectionIdentifier(containingItem: firstApp)
            else { return nil }
            if section.title.isEmpty { return nil }
            sectionHeader.title.text = section.title
            sectionHeader.subtitle.text = section.subtitle
            return sectionHeader
        }
    }

    func configure<T: SelfConfiguringCell>(_ cellType: T.Type, with item: Item, for indexPath: IndexPath) -> T {
        guard let cell = collectionView.dequeueReusableCell(
            withReuseIdentifier: cellType.reuseIdentifier,
            for: indexPath) as? T else {
            fatalError("Unable to dequeue \(cellType)")
        }
        cell.configure(with: item)
        return cell
    }
    
    func reloadData() {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
        snapshot.appendSections(sections)
        for section in sections { snapshot.appendItems(section.item, toSection: section) }
        dataSource?.apply(snapshot)
    }
    
    func createCollectionView() {
        
        collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createCompositionalLayout())
        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        collectionView.backgroundView = backgroundView
        collectionView.isScrollEnabled = false
        view.addSubview(collectionView)
        
        collectionView.register(SectionHeader.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: SectionHeader.reuseIdentifier)
        
        collectionView.register(CarouselCell.self, forCellWithReuseIdentifier: CarouselCell.reuseIdentifier)
        
        createDataSource()
        reloadData()
    }

    func createCompositionalLayout() -> UICollectionViewLayout {
        
        let layout = UICollectionViewCompositionalLayout { sectionIndex, layoutEnvironment in
            let section = self.sections[sectionIndex]
            switch section.identifier {
                
            case "featuredCell": return self.featuredSection(using: section)
                
            default: return self.carouselSection(using: section)
            }
        }
        
        return layout
    }
    
    func sectionHeader() -> NSCollectionLayoutBoundarySupplementaryItem {
        
        let layoutSectionHeaderSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.93), heightDimension: .estimated(80))
        let layoutSectionHeader = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: layoutSectionHeaderSize, elementKind: UICollectionElementKindSectionHeader, alignment: .top)
        return layoutSectionHeader
    }
    
    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(1), heightDimension: .absolute(1))
        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)
            }
            
            // no apparent built-in property of "scrollable content size"
            // so let's jump through some hoops to calculate it
            // we could probably make these class properties, and only update them
            // on frame change, but that's for another time...
                        
            let n: Int = self.dataSource!.snapshot().numberOfItems

            let cvw: CGFloat = self.collectionView.frame.width
            let cw: CGFloat = environment.container.contentSize.width

            let groupWidth: CGFloat = layoutGroupSize.widthDimension.dimension
                        
            // because layoutSection.orthogonalScrollingBehavior  is set to .groupPagingCentered
            // the actual scrollable width is center of item 0 to center of last item
            let scrollableWidth: CGFloat = groupWidth * CGFloat(n - 1)

            // get the group midX
            let groupMidX = CGRect(origin: .zero, size: environment.container.contentSize).midX
                        
            // calculate x-offset for first centered group
            let baseOffsetX: CGFloat = (groupMidX - (groupWidth * 0.5)) - ((cvw - cw) * 0.5)
                        
            // percent we are currently scrolled, of total scrollable width
            let pctScrolled: CGFloat = (offset.x + baseOffsetX) / scrollableWidth

            // tell the background view to update its image view x-offset
            self.backgroundView.xPercent = pctScrolled
        }
        
        return layoutSection
    }

without navigation bar: enter image description here

using navigation bar: enter image description here

In this case, the navigation bar covers the background and the collection view, but the collection view cells are moved down. How to fix it?


Solution

  • Set the collection view's contentInsetAdjustmentBehavior to .never.