Search code examples
iosswiftcore-datauicollectionviewviewdidload

Can I load a UICollectionView's data in ViewDidLoad?


My app's startup screen is a collection view. If the users choose, he/she can "lock" the app using Face ID/Touch ID at app launch. I do this by presenting a view controller that contains a UIVisualEffectView on top of my collection view controller. I screenshot my collection view controller in viewDidLoad, then put the screenshot in a UIImageView beneath the UIVisualEffectView.

The problem is, the collection view is not loaded when the screenshot is taken and the UIVisualEffectView controller appears. The screenshot contains the navigation bar, but the content of the view is just black. I call the function to display the visual effect view controller in the collection view's viewDidLoad function.

Is there a way to load the collection view's data before I take a screenshot in viewDidLoad? The data is stored in Core Data. I have already tried moving the Core Data functions from viewWillAppear to viewDidLoad, but that also didn't work.

EDIT: I was able to solve my problem, but not by loading my UICollectionView's data in ViewDidLoad, as that seems to be impossible. I added a UIVisualEffectView directly on top of my UICollectionView. I also added it as a subview to UIApplication.shared.keyWindow so that it would appear above the navigation bar. The edited code is down below.

Here is my code:

For the collection view controller:

import UIKit
import CoreData
import LocalAuthentication

var ssImage: UIImage?

class AlbumViewController: UIViewController {

// MARK: - Properties
@IBOutlet weak var albumCollectionView: UICollectionView!

var albums: [NSManagedObject] = []

// MARK: - Actions

func getScreenShot()-> UIImage? {

    var screenshotImage :UIImage?
    let layer = UIApplication.shared.keyWindow!.layer
    let scale = UIScreen.main.scale
    UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, scale);
    guard let context = UIGraphicsGetCurrentContext() else {return nil}
    layer.render(in:context)
    screenshotImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    ssImage = screenshotImage
    print("Got screenshot.")
    return screenshotImage
}

// ViewDidLoad and ViewWillAppear
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    //Core Date functions
    guard let appDelegate =
        UIApplication.shared.delegate as? AppDelegate else {
            return
    }
    let managedContext =
        appDelegate.persistentContainer.viewContext
    let fetchRequest =
        NSFetchRequest<NSManagedObject>(entityName: "Album")
    let sortDescriptor = NSSortDescriptor(key: "albumName", ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))
    fetchRequest.sortDescriptors = [sortDescriptor]
    do {
        albums = try managedContext.fetch(fetchRequest)
    } catch let error as NSError {
        print("Could not fetch. \(error), \(error.userInfo)")
    }

    //Setup
    self.albumCollectionView.reloadData()

}

override func viewDidLoad() {
    super.viewDidLoad()

    if AppSettings.requiresLogin == true {

        getScreenShot()
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let rootController = storyboard.instantiateViewController(withIdentifier: "authenticateVC") as! LockedLaunchVC
        self.present(rootController, animated: false, completion: nil)
    }
}

//Core Data functions
func save(name: String) {
    guard let appDelegate =
        UIApplication.shared.delegate as? AppDelegate else {
            return
    }
    let managedContext =
        appDelegate.persistentContainer.viewContext
    let entity =
        NSEntityDescription.entity(forEntityName: "Album",
                                   in: managedContext)!
    let albumName = NSManagedObject(entity: entity,
                                    insertInto: managedContext)
    albumName.setValue(name, forKeyPath: "albumName")
    do {
        try managedContext.save()
        albums.append(albumName)
    } catch let error as NSError {
        print("Could not save. \(error), \(error.userInfo)")
    }
}
}

//Collection View functions
extension AlbumViewController: UICollectionViewDataSource, UICollectionViewDelegate {

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return albums.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let reuseIdentifier = "AlbumCell"
    // get a reference to our storyboard cell
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! AlbumsViewCell
    //Core Data methods
    let albumName = albums[indexPath.row]
    cell.albumNameLabel?.text = albumName.value(forKeyPath: "albumName") as? String
    //Return the finished cell
    return cell
}
}

For the UIVisualEffectView controller:

import UIKit
import LocalAuthentication

class LockedLaunchVC: UIViewController {

    let bioIDAuth = BiometricIDAuth()

    @IBOutlet weak var screenshotImageView: UIImageView!
    @IBAction func unlockButtonTapped(_ sender: UIButton) {
        authAndDismiss()
    }

    func authAndDismiss(){
        bioIDAuth.authenticateUser() {
            self.dismiss(animated: true, completion: nil)
            print("Locked VC dismissed.")
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        self.modalTransitionStyle = .crossDissolve
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        screenshotImageView.image = ssImage
    }
}

EDIT: Revised code for my collection view controller:

import UIKit
import CoreData

class AlbumViewController: UIViewController {

// MARK: - Properties
@IBOutlet weak var albumCollectionView: UICollectionView!
@IBOutlet weak var lockedBlurView: UIVisualEffectView!
@IBOutlet weak var lockedLabelView: UIView!
@IBOutlet weak var lockedLabel: UILabel!

var albums: [NSManagedObject] = []

let bioIDAuth = BiometricIDAuth()

// MARK: - Actions

@IBAction func unwindToAlbumsScreen(sender: UIStoryboardSegue) {
}

@IBAction func unlockButtonTapped(_ sender: UIButton) {
    bioIDAuth.authenticateUser() {
        UIView.animate(withDuration: 0.25, animations: {self.lockedBlurView.alpha = 0})
    }

}

// ViewDidLoad and ViewWillAppear
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    //Core Date functions
    guard let appDelegate =
        UIApplication.shared.delegate as? AppDelegate else {
            return
    }
    let managedContext =
        appDelegate.persistentContainer.viewContext
    let fetchRequest =
        NSFetchRequest<NSManagedObject>(entityName: "Album")
    let sortDescriptor = NSSortDescriptor(key: "albumName", ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))
    fetchRequest.sortDescriptors = [sortDescriptor]
    do {
        albums = try managedContext.fetch(fetchRequest)
    } catch let error as NSError {
        print("Could not fetch. \(error), \(error.userInfo)")
    }

    //Setup to do when the view will appear
    self.albumCollectionView.reloadData()

}

override func viewDidLoad() {
    super.viewDidLoad()
    lockedLabelView.layer.cornerRadius = 12
    switch bioIDAuth.biometricType() {
    case .faceID:
        lockedLabel.text = "Albums are locked.  Tap anywhere to use Face ID to unlock."
    case .touchID:
        lockedLabel.text = "Albums are locked.  Tap anywhere to use Touch ID to unlock."
    default:
        lockedLabel.text = "Albums are locked.  Tap anywhere to use your passcode to unlock."
    }

    if AppSettings.requiresLogin == false {
        lockedBlurView.alpha = 0
    }
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    if AppSettings.requiresLogin == true {
        let curWin = UIApplication.shared.keyWindow
        curWin?.addSubview(lockedBlurView)
    }
}

//Core Data functions
func save(name: String) {
    guard let appDelegate =
        UIApplication.shared.delegate as? AppDelegate else {
            return
    }
    let managedContext =
        appDelegate.persistentContainer.viewContext
    let entity =
        NSEntityDescription.entity(forEntityName: "Album",
                                   in: managedContext)!
    let albumName = NSManagedObject(entity: entity,
                                    insertInto: managedContext)
    albumName.setValue(name, forKeyPath: "albumName")
    do {
        try managedContext.save()
        albums.append(albumName)
    } catch let error as NSError {
        print("Could not save. \(error), \(error.userInfo)")
    }
}
}

//Collection View functions
extension AlbumViewController: UICollectionViewDataSource, UICollectionViewDelegate {

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return albums.count
}

// make a cell for each cell index path
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let reuseIdentifier = "AlbumCell"
    // get a reference to our storyboard cell
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! AlbumsViewCell
    //Core Data methods
    let albumName = albums[indexPath.row]
    cell.albumNameLabel?.text = albumName.value(forKeyPath: "albumName") as? String
    //Return the finished cell
    return cell
}

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    let albumNameFromCell = albums[indexPath.row]
    albumNameTapped = albumNameFromCell.value(forKeyPath: "albumName") as! String
    self.performSegue(withIdentifier: "albumCellTappedSegue", sender: self)
}
}

Solution

  • It looks like you can't load any view's content in viewDidLoad. According to the Apple View Controller Programming Guide, "viewDidLoad is called when the view controller’s content view (the top of its view hierarchy) is created and loaded from a storyboard." Basically, it's called when the view controller and its subviews are loaded, but not visible. ViewDidAppear is where the view controller and its subviews are made visible. According to the Apple View Controller Programming Guide, "viewDidAppear is called just after the view controller’s content view has been added to the app’s view hierarchy." In other words, the view controller and its subviews have been made visible.

    I was able to solve my problem, although the answer to it doesn't answer this question. I added the answer to that problem as an edit to my question.