flomSStaar / ucaverage-ios

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

UCAverage

Build Status

Overview

This project is a calculator developed with SwiftUI that calculates your average. It allows you to display your notes in each block and each subject. A color code is in place to let you know if you have a good grade or not. You can simulate your notes by adjusting the sliders.

This project uses an MVVM architecture adapted for SwiftUI. You can find some explanations here.

You can find the instructions here.

Screenshots

Features

  • List all blocks
  • List all units
  • Detail of one unit
  • List courses in unit
  • Capsule
    • Adjust grade of the course
    • Lock Capsule to lock the grade
    • Color code
  • Edit unit
    • Name
    • Coefficient
    • Add course in unit
    • Edit course in unit
    • Delete course in unit
  • Json persistence
  • Navigation
  • Pages
  • Custom Views
  • Bindings (State, Binding, StateObject, ObservedObject)
  • Notifications with Hashable
  • Generic Viewmodel for editing

MVVM Architecture

In this section, I will explain the concept of MVVM architecture in SwiftUI. All the architecture is explained here.

Model

Apple strongly recommends using structures in Swift. So we will follow the guidelines to write our model.

@startuml

protocol Identifiable
protocol Equatable
protocol Codable

note as n1
    All the structures implements the
    protocols Identifiable, Equatable and Codable
end note

n1 .. Identifiable
n1 .. Codable
n1 .. Equatable

struct Block {
    + id: UUID
    + name: String
    + average: Float

    + init(id: UUID, name: String, units: [UCAUnit])
    + init(id: UUID, name: String)
    + init(name: String, units: [UCAUnit])
    + init(name: String)

    + updateUnit(unit: UCAUnit)
}

note left of Block::average
    This field is a computed value
    that calculate the weighted
    average of all units in the block.
end note

struct UCAUnit {
    + id: UUID
    + name: String
    + coef: Int
    + average: Float

    + init(id: UUID, name: String, coef: Int, courses: [Course])
    + init(id: UUID, name: String, coef: Int)
    + init(name: String, coef: Int, courses: [Course])
    + init(name: String, coef: Int)

    + addCourse(name: String, coef: Int, mark: Float)
    + updateCourse(course: Course)
    + removeCourse(id: UUID)

    + same(other: Course): Bool
}

note left of UCAUnit::average
    This field is a computed value
    that calculate the weighted
    average of all the courses in the unit.
end note

struct Course {
    + id: UUID
    + name: String
    + coef: Int
    + mark: Float

    + init(id: UUID, name: String, coef: Int, mark: Float)
    + init(name: String, coef: Int, mark: Float)

    + same(other: Course): Bool
}

protocol DataManagerProtocol {
    load(): [Block]
    save(blocks: [Block])
}

UCAUnit --> "courses *" Course
Block --> "units *" UCAUnit

@enduml

Views

The views will bind to the property wrapped by the viewmodel and will send user actions to the viewmodel to execute logics.

The main view, in this project HomePage, will own the top viewmodel OdinVm as @StateObject. This viewmodel will be passed to other views with @ObservedObject.

@startuml

class OdinVm
class BlockVm
class UnitVm
class CourseVm

struct HomePage {
    + @StateObject odinVm: OdinVm
}
struct UEDetailPage
struct BlocksView {
    + @ObservedObject odinVm: OdinVm
}
struct UEListView {
    + @ObservedObject blockVm: BlockVm
}
struct BlockRowView
struct UESummaryView
struct UEEditView
struct MarkSlider
struct CourseView
struct CourseItemView
struct CourseEditView

HomePage -up-> OdinVm
HomePage ..> BlocksView
HomePage ..> UEListView

BlocksView -up-> OdinVm
BlocksView ..> BlockRowView

BlockRowView -up-> BlockVm

UEListView -up-> BlockVm
UEListView ..> UESummaryView
UEListView ..> UEDetailPage

UESummaryView -up-> UnitVm
UESummaryView ..> MarkSlider

UEDetailPage -up-> UnitVm
UEDetailPage ..> CourseView
UEDetailPage ..> UEEditView

CourseView -up-> CourseVm
CourseView ..> CourseItemView
CourseItemView ..> MarkSlider

CourseItemView -up-> CourseVm

UEEditView -up-> UnitVm
UEEditView ..> CourseEditView


@enduml

ViewModels

Viewmodels in Swift is composed of two main points:

  • Wrapping
  • Editing
  • Notifications

Even if structures are heavily used and advised in Swift, viewmodels have to be classes in order to use the observable mechanism.

@startuml

protocol ObservableObject

struct Course
struct UCAUnit
struct Block

class OdinVm {
    + @Published blocks: [BlockVm]
}
class BlockVm
class UnitVm
class CourseVm

OdinVm .up.|> ObservableObject
BlockVm .up.|> ObservableObject
UnitVm .up.|> ObservableObject
CourseVm .up.|> ObservableObject

OdinVm --> "*" BlockVm

BlockVm --> "*" UnitVm
BlockVm --> "1" Block
Block --> "*" UCAUnit

UnitVm --> "*" CourseVm
UnitVm --> "1" UCAUnit
UCAUnit --> "*" Course
CourseVm --> "1" Course

@enduml

Model wrapping

The first goal of the Viewmodel is to wrapped the model and all properties of the model in order to separate the View from the Model and the Model from the View.

All viewmodels must conform to the ObservableObject protocol. This protocol indicates that the object conforming to it, is attached to a publisher that emits nofitications just before the object has changed.

All properties in the Viewmodel must be mark @Published to benefiting from the notification system.

Editing

The editing part will provide a copy of the actuel Viewmodel, this copy will be used by the edition view as binding property. The user will entered in edition mode with onEditing() method then the copy property will be set with a copy of the actuel model wrapped in the viewmodel. At the end of edition, the viewmodel will copy the model of the copy into himself to update its model with the new model.

The viewmodel provide a isEditing property to know the state of the edition.

@startuml

class CourseVm {
    + isEditing: Bool

    + copy: CourseVm?

    + onEditing()
    + onEdited(cancel: Bool)
}

@enduml

Notifications

The notification part is to set up the Observer/Observable pattern. This pattern allows to send notifications to an unknown object which has subscribed to notifications.

In Swift, we will write this pattern with callbacks.

When the view updates the viewmodel by the properties, the viewmodel changes the property of the wrapped model thanks to the didSet of the property. After that, it will fire the onModelChanged() to tell to the subscribers that the model has changed.

@startuml

class BaseVm {
    - updatedCallbacks: [(BaseVm) -> ()]

    + addUpdatedCallback(callback: (BaseVm) -> ())
    + onModelChanged()
}

@enduml

Getting Started

Prerequisites

  • iOS 13
  • XCode 14.3
  • iOS Simulator or iOS Device

Installation

  1. Clone the repo
    git clone https://codefirst.iut.uca.fr/git/florent.marques/ucaverage-ios.git
  2. Open the project with XCode
  3. Run the project on iOS Simulator or iOS Device
  4. Enjoy your application

Authors

  • Florent Marques
    • Florent Marques
    • Florent Marques

About


Languages

Language:Swift 100.0%