Create a Note App with Swift and Core Data
Tags: ios, swift, coredata, uicollectionviewlistlayout
Published: 2022-05-11 21:27:30

In this tutorial, I’m going to walk you through how to create a note iOS app.

What you’ll learn:

Create UIKit layout programmatically
Data Persistence with Core data - Create, Read, Update and Delete notes.
Dismiss presented view controller when UIAlertAction is tapped.

Let’s Get Started!


![Create A New Project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sdxmmb3txmam35i9816r.png)

In your Main.storyboard file, Embed the View Controller inside a Navigation View Controller.


![Embed View Controller inside a Navigation Controller](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a4k50zyp9qpifp85dfj3.png)

Click on the Navigation bar of the Navigation View Controller and toggle the Inspection Pane Open and under the style option, check prefer large titles. 
Importantly, In the inspector pane, make sure the Navigation Controller has “Is Initial View Controller” option checked.


![Set Large title](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/38jisnb00d89ol75gc7t.png)

Now, let’s rename our default view controller created for us to `NotesViewController` To do that, Option click the ViewController class name as follows: 


![Rename View Controller](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/negysixz96rjsj7sqta5.png)

Inside our `viewDidLoad` method, let give the `NotesViewController` a title and a RightBarButtonItem to add new notes:

```
       title = "Notes"
       navigationItem.rightBarButtonItem = UIBarButtonItem(
            barButtonSystemItem: .add, 
            target: self, 
            action: #selector(didTapAddButton)
       )
```

Note let’s create a private `@objc` function to handle our bar button item.

```
    @objc
    private func didTapAddButton() {
    
    }
```
Let's run our app to see our progress. It should look something like this: 

![First App Run](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oxgbriuwpmnubzy32keb.png)

You should see either of the screens above depending on whether your device is on light or dark mode. Cool 

Now let’s go ahead to create the Add Note ViewController. To do that, create a new Cocoa Touch Class. Make sure not to check the “Also create XIB file” option, unless you’re confident about your Interface Builder skills.

![Create AddNoteViewController](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nhjsd7uap9o1mqktsx5h.png)

Now set the background colour for the `AddNoteViewController` to .systemBackground in side the `viewDidLoad` method as follows:

`view.backgroundColor = .systemBackground`

Without that, the view controller would be transparent and you wouldn't know it's presented.

Great! Now let's go back to the NotesViewController and add the following code snippet to the private method we created, `didTapAddButton`: 

```
let addNoteVC = AddNoteViewController()
let navVC = UINavigationController(rootViewController: addNoteVC)
navVC.navigationBar.prefersLargeTitles = true
navVC.modalPresentationStyle = .formSheet
present(navVC, animated: true, completion: nil)
```
Don't worry too much about the NavigationController. I added it because I wanted the view controller to have a Navigation bar.

Next thing is to create views on the add note view controller, but before that, let's create some convenience properties and method as an extension to UIView 

Create a new swift file named Extensions and add the following: 

```
    import UIKit
    
    extension UIView {
        /// Weight of view
        var width: CGFloat {
            frame.size.width
        }
        
        /// Height of view
        var height: CGFloat {
            frame.size.height
        }
        
        /// Left edge of view
        var left: CGFloat {
            frame.origin.x
        }
        
        /// Right edge of view
        var right: CGFloat {
            left + width
        }
        
        /// Top edge of view
        var top: CGFloat {
            frame.origin.y
        }
        
        /// Bottom edge of view
        var bottom: CGFloat {
            top + height
        }
        
        func addSubViews(views: UIView...) {
            for view in views {
                self.addSubview(view)
            }
        }
    }
```
Now let’s create our views inside Add Note ViewController:

First add the title textField as an anonymous closure.

```
private var titleField: UITextField = {
    let field = UITextField()
    field.placeholder = "Title"
    field.textColor = .label
    field.font = UIFont.systemFont(ofSize: 20, weight:.medium)
    return field
}()
```
Then add the body text view.

```
    private var bodyTextView: UITextView = {
        let view = UITextView()
        view.text = "Type in here..."
        view.font = UIFont.systemFont(ofSize: 18)
        view.textColor = .placeholderText
        view.clipsToBounds = true
        return view
    }()

```
Now override the `viewWillLayoutSubviews` by defining frame for our views in the view controller’s view.

First add the views as subviews to the controller's main view as follows:

`view.addSubViews(views: titleField, bodyTextView)`

Remember we created a convenience method as an extension of UIView called `addSubviews()` which can take a varying number of views instead of calling addSubview multiple times.

```
titleField.frame = CGRect(
     x: 20, 
     y: 120, 
     width: view.width - 40, 
     height: 44
)
bodyTextView.frame = CGRect(
     x: 16, 
     y: titleField.bottom + 20, 
     width: view.width - 32, 
     height: view.bottom - 250
)
```
Now add the following inside `viewDidLoad`

```
    title = "Add Note"
    navigationItem.rightBarButtonItem = UIBarButtonItem(
            title: "Save", 
            style: .done, 
            target: self, 
            action: #selector(didTapSaveButton)
    )
    bodyTextView.delegate = self
    titleField.delegate = self
```
We have now added a `rightBarButtonItem` to the navigation bar which we’ll tap to save the note.

Now create the `didTapSaveButton` method:

```
@objc
private func didTapSaveButton() {

}
```
Your editor pane would be showing some errors right now, because we need to conform to the `UITextFieldDelegate` and the `UITextViewDelegate` 

Now create an extension of the `AddNoteViewController` at the bottom of the screen outside the `AddNoteViewController` class as follows:

```
extension AddNoteViewController: UITextFieldDelegate, UITextViewDelegate {
    
}
```
Now when you build and run, we should have something that looks as follows:

![Add Note VC](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ltxhy3e1poiw22pvte1.png)

Now inside the extension, let implement the delegate functions as follows:

```
    func textFieldDidEndEditing(_ textField: UITextField) {
        titleField.resignFirstResponder()
        if textField == titleField && 
            !titleField.text!.isEmpty {
            bodyTextView.becomeFirstResponder()
        }
    }
    
    func textFieldShouldReturn(_ textField: UITextField) -> 
        Bool {
        titleField.resignFirstResponder()
        bodyTextView.becomeFirstResponder()
        return true
    }
    
    func textViewDidBeginEditing(_ textView: UITextView) {
        if textView == bodyTextView && 
           bodyTextView.text == "Type in here..." {
            textView.text = ""
            bodyTextView.textColor = .label
        }
    }
    
    func textViewDidEndEditing(_ textView: UITextView) {
        if textView == bodyTextView && 
           bodyTextView.text.isEmpty {
            textView.text = "Type in here..."
            bodyTextView.textColor = .placeholderText
        }
    }
```
So far we've been working on the UI, now to the part we've been waiting for.

## PERSISTING DATA TO CORE DATA.
Core data is Apples framework for managing object graph and persisting data to disk. Although core data can persist data to disk, it is mainly for managing object graph in your application.

To use core data in our app, we need to first create a “Data Model file”. This is to define the structure of the necessary objects. 
Remember when creating our project, we had the “Use core data” Option checked which creates this Data model file for us by default.

![Data Model File](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zeukyejsfz5a3cgqbn1o.png)

Now click on the Add Entity button and rename that to Note. Afterwards add attributes (with names and types) by clicking the plus button.
Lastly, toggle open your inspector pane AND CHANGE THE “Codegen” Option to “Manual/None” because we’ll like to create our model class ourselves. 
You’ll see the reason for this in a sec.

Now let’s create a swift file named “model”. You could name it anything, I’m just naming it that since there’re not too many model in this sample app.
Inside the model file, let’s create an Enum and a class for our entity.

```
    import CoreData
    
    enum Section: Hashable {
        case main
    }

    @objc(Note)
    public class Note: NSManagedObject {
        
        @NSManaged public var title: String
        @NSManaged public var body: String
        @NSManaged public var created: Date

    }
    
    extension Note {
        @nonobjc public class func fetchRequest() -> 
             NSFetchRequest<Note> {
            NSFetchRequest<Note>(entityName: "Note")
        }
    }
```
NSManagedObject is like an instance of your Entity (which can be liken to a class).

We’ll be using a Diffable DataSource and UICollectionView later on in our `NotesViewController`. The enum represents our section for the CollectionView.

Now that we’ve created an entity, let’s go back to our `AddNoteViewController` to complete adding notes to our app. Inside your `didTapSaveButton` method, let add the following snippet: 

```
if titleField.text!.isEmpty || bodyTextView.text.isEmpty {
   let alertController = UIAlertController(
       title: "Fields Required",
       message: "Please enter a title and body for your 
                 note!",
       preferredStyle: .alert
   )
            
   let cancelAction = UIAlertAction(
       title: "Ok", 
       style: .cancel, 
       handler: nil
   )
   alertController.addAction(cancelAction)
            
   present(alertController, animated: true)
   return
}
        
guard let appDelegate = UIApplication.shared.delegate 
     as? AppDelegate else { return }
        
let managedContext = appDelegate
                        .persistentContainer
                        .viewContext

let note = Note(context: managedContext)

note.title = titleField.text!
note.body = bodyTextView.text
note.created = Date.now
            
do {
    try managedContext.save()
            
    let alertController = UIAlertController(
         title: "Note Saved", 
         message: "Note has been saved successfully!", 
         preferredStyle: .alert
    )

    let okayAction = UIAlertAction(
          title: "Ok", 
          style: .cancel) { [weak self] _ in
       guard let self = self else { return }
       self.dismiss(animated: true) {
           self.dismiss(animated: true, completion: nil)
       }
    }

    alertController.addAction(okayAction)
    present(alertController, animated: true)
            
} catch let error as NSError {
    fatalError("Error saving person to core data. 
             \(error.userInfo)")
}
```
Wooops… Great one! 

Now when we run our app and tap on the add button to add a note we’ll be able to add a new note and dismiss on success.

One last thing before we go back to our Home Screen (`NotesViewController`) which shows a list of all the notes we’ve added.

Create a custom delegate to notify the `NotesViewController` that we’ve successfully added a new note.

At the very top of the `AddNoteViewController`, just before the class definition, Add the following snippet:

```
    protocol AddNoteViewControllerDelegate {
        func didFinishAddingNote()
    }
```
Now, inside the `AddNoteViewController`, let’s create delegate property and call the delegate function inside the `okayAction` of the `didTapSaveButton` method as follows:

```
class AddNoteViewController: UIViewController {
    
    var delegate: AddNoteViewControllerDelegate?

    ...

    @objc
    private func didTapSaveButton() {       
      ...
      let okayAction = UIAlertAction(
        title: "Ok",
        style: .cancel) { [weak self] _ in
            guard let self = self else { return }

            self.delegate?.didFinishAddingNote() 
            
            self.dismiss(animated: true) {
                self.dismiss(animated: true, completion: nil)
            }
        }
        ...
    }    
}
```

## WORKING WITH UICOLLECTIONVIEW LIST LAYOUT

Inside our NotesViewController, let add the following:

```
private var dataSource: 
    UICollectionViewDiffableDataSource<Section, Note>! = nil
private var notesCollectionView: UICollectionView! = nil    
private var notes: [Note]?

```
Remember we create the Section and Note model, in our model.swift file.
Now, let configure the Collection View we’ve created by adding the following:

```
private func createLayout() -> UICollectionViewLayout {
    let config = UICollectionLayoutListConfiguration(
                    appearance: .plain)
    return UICollectionViewCompositionalLayout.list(
                    using: config)
}
    
private func configureCollectionView() {
    notesCollectionView = UICollectionView(
              frame: view.bounds, 
              collectionViewLayout: createLayout()
    )
    notesCollectionView.autoresizingMask = 
              [.flexibleWidth, .flexibleHeight]
    view.addSubview(notesCollectionView)
    notesCollectionView.delegate = self
}
```
Now you need to conform to the UICollectionViewDelegate protocol, add the following outside the class definition of the NotesViewController:

```
extension NotesViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, 
        didSelectItemAt indexPath: IndexPath) {      
    }
}
```
We have to conform to the `didSelectItemAt` indexPath method to go to the note detail screen.

Now, we’ve successfully configured our collection view, let’s also configure the dataSource. To do that, add the following method to NotesViewController:

```
private func configureDataSource() {
    let cellRegistration = UICollectionView
        .CellRegistration<UICollectionViewListCell, Note> { 
          cell, indexPath, note in
    var content = cell.defaultContentConfiguration()
    content.text = note.title
    content.textToSecondaryTextVerticalPadding = 8
    content.textProperties.font = UIFont.systemFont(
         ofSize: 18, 
         weight: .medium
    )
    content.textProperties.color = .label
    content.secondaryTextProperties.font = 
                  UIFont.systemFont(ofSize: 16)
    content.secondaryTextProperties.color = .secondaryLabel
            
    let bodyTextArray = note.body.components(separatedBy: " ")
            
    if (bodyTextArray.count > 8) {
       var bodyText = bodyTextArray[0...8]
                         .joined(separator: " ")
       bodyText.append("...")
       content.secondaryText = bodyText
    } else {
       content.secondaryText = note.body            
    }
            
    cell.contentConfiguration = content
    cell.accessories = [.disclosureIndicator()]
  }
        
dataSource = UICollectionViewDiffableDataSource<Section, Note> 
       (collectionView: notesCollectionView) {
       (collectionView: UICollectionView, 
        indexPath: IndexPath, 
        note: Note) -> UICollectionViewCell? in
    return collectionView.dequeueConfiguredReusableCell(
        using: cellRegistration, 
        for: indexPath, 
        item: note
    )
}
        
    var snapshot = 
      NSDiffableDataSourceSnapshot<Section, Note>()
     snapshot.appendSections([.main])
        
    if let notes = notes {
        snapshot.appendItems(notes)
    }
    dataSource.apply(snapshot, animatingDifferences: true)
}
```
First, we registered the cell for the UICollection View and then configure the data source.

Build and run your app to be sure there’s no error.

Now let add the following two methods to our controller. First to fetch the data from Core Data and the other to update our UI

```
private func fetchNotes() {
    guard let appDelegate = UIApplication.shared.delegate 
        as? AppDelegate else { return }
    let managedContext = 
        appDelegate.persistentContainer.viewContext

    do {
        notes = try managedContext.fetch(Note.fetchRequest())
    } catch let error as NSError {
       fatalError("Unable to fetch. \(error) = 
              \(error.userInfo)")
    }
}
    
private func updateCollectionView() {
    guard let notes = notes else {
        return
    }
    var snapshot = dataSource.snapshot()
    snapshot.appendItems(notes)
    dataSource.apply(snapshot, animatingDifferences: true)
}
```
Great work!

Now let’s call the `configureCollectionView` and `configureDataSource` methods from inside `viewDidLoad` as follows:

```
 override func viewDidLoad() {
     super.viewDidLoad()
        
     ...
        
     configureCollectionView()
     configureDataSource()
 }
```
Now override the `viewWillAppear` method and call the `fetchNotes` method from inside it.

```
override func viewWillAppear(_ animated: Bool) {
    fetchNotes()
}
```
Also override the `viewDidAppear` method and call the `updateCollectionView` method from inside it.

```
override func viewDidAppear(_ animated: Bool) {
    updateCollectionView()
}
```
Great!!!

Now we can run our app. If everything is okay, we should see the following.

![NotesViewController showing data](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hw8w0h65hhzprnk5w4mc.png)

Finally, before we wrap it up for this part 1, remember we created a delegate in our `AddNoteViewController` which should notify the `NotesViewController` after adding a new note to the database. 

Remember, we navigate to AddNoteVC by calling the `didTapAddButton` method. Now set the delegate for the “addNoteVC” instance as follows:

```
    @objc
    private func didTapAddButton() {
        let addNoteVC = AddNoteViewController()
        addNoteVC.delegate = self        
        ...
    }
```
This will require us to conform to the `AddNoteViewControllerDelegate` and implement the 	`didFinishAddingNote` method as follows:

```
extension NotesViewController: AddNoteViewControllerDelegate {
    func didFinishAddingNote() {
        fetchNotes()
        updateCollectionView()
    }
}
```
We again, call the `fetchNotes` and `updateCollectionView` methods from inside there.

Great! Now when we create a new note, it immediately update the NotesViewController.

Here's a link to [source code](https://github.com/TayoDavid/Notes) on GitHub.

In the [Second Part of this article](https://dev.to/omotayodavid/create-a-note-app-with-swift-and-core-data-ii-28el), we’ll cover how to update and delete notes.