I want to use a Combine in my project and face the problem.
Here is the code of the ViewController
import Combine
import UIKit
class ProfileDetailsController: ViewController {
//
// MARK: - Views
#IBOutlet private var tableView: UITableView!
// MARK: - Properties
private typealias DataSource = UITableViewDiffableDataSource<ProfileDetailsSection, ProfileDetailsRow>
private typealias Snapshot = NSDiffableDataSourceSnapshot<ProfileDetailsSection, ProfileDetailsRow>
#Published private var data: [ProfileDetailsSectionModel] = {
return ProfileDetailsSection.allCases.map { ProfileDetailsSectionModel(section: $0, data: $0.rows) }
}()
private lazy var dataSource: DataSource = {
let dataSource = DataSource(tableView: tableView) { tableView, _, model in
let cell = tableView.dequeueReusableCell(withIdentifier: TextFieldTableCell.name) as! TextFieldTableCell
cell.delegate = self
cell.setData(model: model)
return cell
}
dataSource.defaultRowAnimation = .fade
return dataSource
}()
}
// MARK: - Setup binding
extension ProfileDetailsController {
override func setupBinding() {
tableView.registerCellXib(cell: TextFieldTableCell.self)
$data.receive(on: RunLoop.main).sink { [weak self] models in
let sections = models.map { $0.section }
var snapshot = Snapshot()
snapshot.appendSections(sections)
models.forEach { snapshot.appendItems($0.data, toSection: $0.section) }
self?.dataSource.apply(snapshot, animatingDifferences: true)
}.store(in: &cancellable)
}
}
// MARK: - Cell delegates
extension ProfileDetailsController: TextFieldTableCellDelegate {
func switcherAction() { }
}
And here is the code of the cell.
import UIKit
protocol TextFieldTableCellData {
var placeholder: String? { get }
}
protocol TextFieldTableCellDelegate: NSObjectProtocol {
func switcherAction()
}
class TextFieldTableCell: TableViewCell {
//
// MARK: - Views
#IBOutlet private var textField: ZWTextField!
// MARK: - Properties
public weak var delegate: TextFieldTableCellDelegate?
override class var height: CGFloat {
return 72
}
}
// MARK: - Public method
extension TextFieldTableCell {
func setData(model: TextFieldTableCellData) {
textField.placeholder = model.placeholder
}
}
ViewController's deinit was not called.
But when I use this code for ViewController
import UIKit
class ProfileDetailsController: ViewController {
//
// MARK: - Views
#IBOutlet private var tableView: UITableView!
// MARK: - Properties
#Published private var data: [ProfileDetailsSectionModel] = {
return ProfileDetailsSection.allCases.map { ProfileDetailsSectionModel(section: $0, data: $0.rows) }
}()
}
// MARK: - Startup
extension ProfileDetailsController {
override func startup() {
tableView.dataSource = self
tableView.registerCellXib(cell: TextFieldTableCell.self)
}
}
// MARK: - Startup
extension ProfileDetailsController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data[section].data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let model = data[indexPath.section].data[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: TextFieldTableCell.name) as! TextFieldTableCell
cell.delegate = self
cell.setData(model: model)
return cell
}
}
// MARK: - Cell delegates
extension ProfileDetailsController: TextFieldTableCellDelegate {
func switcherAction() {}
}
Everything is fine. deinit called. I tried to set dataSource optional and set it nil on deinit, the same result. With Combine deinit called only when I comment this line:
cell.delegate = self
Does anyone know what's the matter?
Xcode 13.2 iOS 15.2
The Combine stuff is a total red herring. That's why you can't locate the problem; you're looking in the wrong place. The issue is the difference between an old-fashioned data source and a diffable data source. The problem is here:
private lazy var dataSource: DataSource = { // *
let dataSource = DataSource(tableView: tableView) { tableView, _, model in
let cell = tableView.dequeueReusableCell(withIdentifier: TextFieldTableCell.name) as! TextFieldTableCell
cell.delegate = self // *
I've starred the problematic lines:
On the one hand, you (self, the view controller) are retaining the dataSource.
On the other hand, you are giving the data source a cell provider function in which you speak of self.
That's a retain cycle! You need to break that cycle. Change
let dataSource = DataSource(tableView: tableView) { tableView, _, model in
To
let dataSource = DataSource(tableView: tableView) { [weak self] tableView, _, model in
(That will compile, because although self is now an Optional, so is cell.delegate.)
Related
I am at the stage of learning new swift and I designed my application as mvc design pattern. I went on an adventure to learn mvvm :D.
There are parts that I still don't understand. I learned that I need to transfer without using UIKit in the ViewModel part, but I couldn't figure out how to transfer it. I have to find the way to it. I have 10 Viewcontroller pages and I want to make them all according to mvvm.
I'm trying to convert my design from MVC to MVVM but i am getting this error how can i solve it?
BreedsViewController
import UIKit
import ProgressHUD
protocol BreedsViewControllerInterface: AnyObject {
func prepareCollectionView()
}
final class BreedsViewController: UIViewController {
#IBOutlet weak var categoryCollectionView: UICollectionView!
// main storyboard collection View adding (dataSource, delegate)
#IBOutlet weak var popularCollectionView: UICollectionView!
// main storyboard collection View adding (dataSource, delegate)
#IBOutlet weak var specialsCollectionView: UICollectionView!
// main storyboard collection View adding (dataSource, delegate)
private lazy var viewModel = BreedsVM()
// data, move mvvm
var categories: [DogCategory] = []
var populars: [Breed] = []
var downCategories:[Breed] = []
override func viewDidLoad() {
super.viewDidLoad()
viewModel.view = self
viewModel.viewDidLoad()
}
private func registerCell() {
categoryCollectionView.register(UINib(nibName: CategoryCollectionViewCell.identifier, bundle: nil), forCellWithReuseIdentifier: CategoryCollectionViewCell.identifier)
popularCollectionView.register(UINib(nibName: DogPortraitCollectionViewCell.identifier, bundle: nil), forCellWithReuseIdentifier: DogPortraitCollectionViewCell.identifier)
specialsCollectionView.register(UINib(nibName: DogLandscapeCollectionViewCell.identifier, bundle: nil), forCellWithReuseIdentifier: DogLandscapeCollectionViewCell.identifier)
}
}
extension BreedsViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
switch collectionView {
case categoryCollectionView:
return categories.count
case popularCollectionView:
return populars.count
case specialsCollectionView:
return downCategories.count
default: return 0
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
switch collectionView {
case categoryCollectionView:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CategoryCollectionViewCell.identifier, for: indexPath) as! CategoryCollectionViewCell
cell.setup(category: categories[indexPath.row])
return cell
case popularCollectionView:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: DogPortraitCollectionViewCell.identifier, for: indexPath) as! DogPortraitCollectionViewCell
cell.setup(breed: populars[indexPath.row])
return cell
case specialsCollectionView:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: DogLandscapeCollectionViewCell.identifier, for: indexPath) as! DogLandscapeCollectionViewCell
cell.setup(breed: downCategories[indexPath.row])
return cell
default: return UICollectionViewCell()
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if collectionView == categoryCollectionView {
let controller = ListDogsViewController.instantiate()
controller.category = categories[indexPath.row]
navigationController?.pushViewController(controller, animated: true)
} else {
let controller = FavoriteDetailViewController.instantiate()
controller.breed = collectionView == popularCollectionView ? populars[indexPath.row] : downCategories[indexPath.row]
navigationController?.pushViewController(controller, animated: true)
}
}
}
extension BreedsViewController: BreedsViewControllerInterface {
func prepareCollectionView() {
registerCell()
ProgressHUD.show()
NetworkService.shared.fetchAllCategories { [weak self] (result) in
switch result {
case.success(let allBreed):
ProgressHUD.dismiss()
self?.categories = allBreed.categories ?? []
self?.populars = allBreed.populars ?? []
self?.downCategories = allBreed.downCategories ?? []
self?.categoryCollectionView.reloadData()
self?.popularCollectionView.reloadData()
self?.specialsCollectionView.reloadData()
case.failure(let error):
ProgressHUD.showError(error.localizedDescription)
}
}
}
}
BreedsVM
import Foundation
protocol BreedsVMInterface {
var view: BreedsViewControllerInterface? { get set }
func viewDidLoad()
func didSelectItemAt(indexPath: IndexPath)
}
final class BreedsVM {
weak var view: BreedsViewControllerInterface?
}
extension BreedsVM: BreedsVMInterface {
func didSelectItemAt(indexPath: IndexPath) {
}
func viewDidLoad() {
view?.prepareCollectionView()
}
}
For example, I want to apply didselectItemAt according to Mvvm. When I want to do this, I get the following error. How can I solve it?
Changed BreedsViewController
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
viewModel.didSelectItemAt(indexPath: indexPath)
}
Changed BreedsVM
import Foundation
protocol BreedsVMInterface {
var view: BreedsViewControllerInterface? { get set }
func viewDidLoad()
func didSelectItemAt(indexPath: IndexPath)
}
final class BreedsVM {
weak var view: BreedsViewControllerInterface?
var categories: [DogCategory] = []
var populars: [Breed] = []
var downCategories:[Breed] = []
}
extension BreedsVM: BreedsVMInterface {
func didSelectItemAt(indexPath: IndexPath) {
if collectionView == categoryCollectionView {
let controller = ListDogsViewController.instantiate()
controller.category = categories[indexPath.row]
navigationController?.pushViewController(controller, animated: true)
} else {
let controller = FavoriteDetailViewController.instantiate()
controller.breed = collectionView == popularCollectionView ? populars[indexPath.row] : downCategories[indexPath.row]
navigationController?.pushViewController(controller, animated: true)
}
}
func viewDidLoad() {
view?.prepareCollectionView()
}
}
BreedsVM's warnings and errors
Cannot find 'categoryCollectionView' in scope Cannot find 'collectionView' in scope Cannot find 'popularCollectionView' in scope
When we move from MVC to any other architecture, we do so to achieve the separation of business logic and UI Logic so for example in MVVM, the ViewModel shouldn't know anything about the UI and also the ViewController should be dumb just makes UI stuff ( changing color, show and hide UI elements, .. ) and also in MVVM, the connection should be from one side the ViewController, the ViewController should have an instance from the ViewModel but the ViewModel should have any reference from the ViewController, but how we achieve the changing of the UI after processing some logic? by binding, and this can be done through number of ways, for example: Combine or RxSwift or even closures, but for simplicity we can start by making the binding using closures so let's take an example:
// ViewModel
class BreedsViewModel {
// MARK: - Closures
var fetchCategoriesSucceeded: ( (_ categories: [DogCategory], _ populars: [Breed], _ downCategories: [Breed]) -> Void )?
var fetchCategoriesFailed: ( (_ errorMessage: String) -> Void )?
// MARK: - Fetch Categories API
func fetchCategories(){
// Also this should be injected to the ViewModel instead of using it as a singleton, read more about dependency injection
NetworkService.shared.fetchAllCategories { [weak self] (result) in
switch result {
case.success(let allBreed):
self?.fetchCategoriesSucceeded?(allBreed.categories, allBreed.populars, allBreed.downCategories)
case.failure(let error):
self?.fetchCategoriesFailed?(error.localizedDescription)
}
}
}
}
// ViewController
class BreedsViewController: UIViewController {
var viewModel = BreedsViewModel() // This should be injected to the view controller
private var categories: [DogCategory] = []
private var populars: [Breed] = []
private var downCategories:[Breed] = []
override func viewDidLoad() {
super.viewDidLoad()
bindViewModel()
fetchCategories()
}
private func fetchCategories(){
// ProgressHUD.show()
viewModel.fetchCategories()
}
private func bindViewModel() {
viewModel.fetchCategoriesSucceeded = { [weak self] categories, populars, downCategories in
// ProgressHUD.dismiss()
self?.categories = categories
self?.populars = populars
self?.downCategories = downCategories
// collectionView.reloadData()
}
viewModel.fetchCategoriesFailed = { [weak self] errorMessage in
// ProgressHUD.showError(errorMessage)
}
}
}
As you can see now, the ViewModel doesn't know anything about the UI, just getting the data from the API then notify the ViewController through the closure and when the ViewController notified, it should update the UI.
I can see also what you are trying to achive is more related to MVP, there are a Presenter and a ViewController, the Presenter will have a weak reference from the ViewController and update the view controller through a delegate
// Presenter
protocol BreedsPresenterDelegate: AnyObject {
func fetchCategoriesSucceeded(_ categories: [DogCategory], _ populars: [Breed], _ downCategories: [Breed])
func fetchCategoriesFailed(_ errorMessage: String)
}
class BreedsPresenter {
weak var delegate: BreedsPresenterDelegate?
func fetchCategories(){
NetworkService.shared.fetchAllCategories { [weak self] (result) in
switch result {
case.success(let allBreed):
self?.delegate?.fetchCategoriesSucceeded(allBreed.categories, allBreed.populars, allBreed.downCategories)
case.failure(let error):
self?.delegate?.fetchCategoriesFailed(error.localizedDescription)
}
}
}
}
// ViewController
class BreedsViewController: UIViewController {
var presenter = BreedsPresenter() // This should be injected to the view controller
private var categories: [DogCategory] = []
private var populars: [Breed] = []
private var downCategories:[Breed] = []
override func viewDidLoad() {
super.viewDidLoad()
presenter.delegate = self
fetchCategories()
}
private func fetchCategories(){
// ProgressHUD.show()
presenter.fetchCategories()
}
}
extension BreedsViewController: BreedsPresenterDelegate {
func fetchCategoriesSucceeded(_ categories: [DogCategory], _ populars: [Breed], _ downCategories: [Breed]) {
// ProgressHUD.dismiss()
self.categories = categories
self.populars = populars
self.downCategories = downCategories
// collectionView.reloadData()
}
func fetchCategoriesFailed(_ errorMessage: String) {
// ProgressHUD.showError(errorMessage)
}
}
I hope this helps.
I have been using the same protocols and functions throughout all projects. But this one is giving me hard times with fatal error.
This is my Nibloadable
protocol NibLoadable: class {
static var nib: UINib { get }
}
extension NibLoadable {
static var nib: UINib {
return UINib(nibName: String(describing: self), bundle: Bundle(for: self))
}
}
This is my Reusable
protocol Reusable: class {
static var reuseIdentifier: String { get }
}
extension Reusable {
static var reuseIdentifier: String {
return String(describing: Self.self)
}
}
in VideoListTableViewCell
static let identifier = String(describing: VideoListTableViewCell.self)
extension VideoListTableViewCell: Reusable, NibLoadable { }
inside my VideoViewController
class VideoViewController: UIViewController, VideoModule.View {
#IBOutlet weak var tableView: UITableView!
var presenter: VideoModule.Presenter!
var videos: [VideoModel]?
override func viewDidLoad() {
super.viewDidLoad()
presenter.fetchVideos()
configureTableView()
}
func configureTableView() {
tableView.register(VideoListTableViewCell.self)
tableView.delegate = self
tableView.dataSource = self
extension VideoViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return configureTableViewCell(indexPath: indexPath)
}
func configureTableViewCell(indexPath: IndexPath) -> VideoListTableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: VideoListTableViewCell.identifier, for: indexPath) as! VideoListTableViewCell
let videoData = (videos?[indexPath.row])!
cell.configure(model: videoData)
return cell
}
So inside configureTableView function at .register it throws fatal error. I have tried adding an identifier to create my cell and register tableView but it also did not solve this problem.
at the .register line my tableView is tableView UITableView? nil none with the error explanation.
And finally my UITableView Extension
extension UITableView {
func register<T: UITableViewCell>(_: T.Type) where T: Reusable, T: NibLoadable {
register(T.nib, forCellReuseIdentifier: T.reuseIdentifier)
}
func dequeueReusableCell<T: UITableViewCell>(forIndexPath indexPath: IndexPath) -> T where T: Reusable {
guard let cell = dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as? T else {
fatalError("Could not dequeue cell with identifier \(T.reuseIdentifier)")
}
return cell
}
I have tried using VIPER so this is my VideoContract
protocol VideoModuleViewProtocol: AnyObject {
var presenter: VideoModule.Presenter! { get set }
}
protocol VideoModuleInteractorProtocol: AnyObject {
var presenter: VideoModule.Presenter? { get set }
func fetchVideos()
}
protocol VideoModulePresenterProtocol: AnyObject {
var view: VideoModule.View? { get set }
var interactor: VideoModule.Interactor! { get set }
var router: VideoModule.Router! { get set }
func fetchVideos()
func didFetch(videos: [VideoModel])
}
protocol VideoModuleRouterProtocol: AnyObject {
}
struct VideoModule {
typealias View = VideoModuleViewProtocol
typealias Interactor = VideoModuleInteractorProtocol
typealias Presenter = VideoModulePresenterProtocol
typealias Router = VideoModuleRouterProtocol
}
I have created storyboard file as VideoListTableViewCell.xib and created both tableviewcell files at the same time. Everything is connected to the correct places too.
How can I overcome this meaningless problem?
I am able to query the data and match it to my model but am not able to display it in my table view. I have 3 files I am working with apart from the storyboard.
Here is the main view controller:
class MealplanViewController: UIViewController {
var db: Firestore!
var mealplanArray = [Mealplan]()
#IBOutlet weak var mealplanTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
mealplanTableView?.dataSource = self
mealplanTableView?.delegate = self
db = Firestore.firestore()
loadData()
// Do any additional setup after loading the view.
}
func loadData() {
userEmail = getUserEmail()
db.collection("Meal_Plans").getDocuments() {querySnapshot , error in
if let error = error {
print("\(error.localizedDescription)")
} else {
self.mealplanArray = querySnapshot!.documents.compactMap({Mealplan(dictionary: $0.data())})
print(self.mealplanArray)
DispatchQueue.main.async {
self.mealplanTableView?.reloadData()
}
}
}
}
func getUserEmail() -> String {
let user = Auth.auth().currentUser
if let user = user {
return user.email!
} else {
return "error"
}
}
}
// MARK: - Table view delegate
extension MealplanViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return mealplanArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//let cell = tableView.dequeueReusableCell(withIdentifier: "MealplanTableViewCell", for: indexPath)
let mealplanRow = mealplanArray[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "MealplanTableViewCell") as! MealplanTableViewCell
cell.setMealplan(mealplan: mealplanRow)
return cell
}
}
And here is the cell where I am showing one of the queried values:
class MealplanTableViewCell: UITableViewCell {
#IBOutlet weak var mealplanNameLabel: UILabel!
func setMealplan(mealplan: Mealplan) {
// Link the elements with the data in here
mealplanNameLabel.text = mealplan.mpName
print(mealplan.mpName)
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
And finally, here is the data model:
import Foundation
import Firebase
protocol MealplanSerializable {
init?(dictionary:[String:Any])
}
struct Mealplan {
var mealplanId:String
var mpName:String
]
}
}
extension Mealplan : MealplanSerializable {
init?(dictionary: [String : Any]) {
guard let
let mealplanId = dictionary["mealplanId"] as? String,
let mpName = dictionary["mpName"] as? String,
else { return nil }
self.init(mealplanId: mealplanId, mpName: mpName)
}
}
I am getting just an empty table view with no data in it.
the tableView dataSource is properly set up in the IB
the viewController identity is properly set as well in the IB
this is my viewModel
class StatusCodeViewModel {
let apiClient = APIClient.shared
var statusCodes: [StatusCode] = []
let identifier = "statusCodeCell"
init() {}
func loadStatusCodes() {
apiClient.execute(service: .statusCode) { statusCodes in
self.statusCodes = statusCodes
}
}
}
and the viewController in which I want to load data
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var viewModel: StatusCodeViewModel? {
didSet {
if viewModel!.statusCodes.count > 0 {
self.tableView.reloadData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
viewModel = StatusCodeViewModel()
viewModel!.loadStatusCodes()
}
}
extension ViewController : UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let statusCodes = viewModel!.statusCodes as? [StatusCode] {
return statusCodes.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: viewModel!.identifier)
cell?.textLabel!.text = viewModel!.statusCodes[indexPath.row].title
return cell!
}
}
the data count is 0 and no data is shown in the tableView
You have did set on view model which will occur on initialisation.
You will have to implement some kind of callback when the api returns the call - easiest way would be protocol.
protocol StatusCodeViewModelDelegate {
func callFinished()
}
class StatusCodeViewModel {
let apiClient = APIClient.shared
var statusCodes: [StatusCode] = []
let identifier = "statusCodeCell"
var delegate : StatusCodeViewModelDelegate?
init() {}
func loadStatusCodes() {
apiClient.execute(service: .statusCode) { statusCodes in
self.statusCodes = statusCodes
delegate?.callFinished()
}
}
}
Then in your viewController:
override func viewDidLoad() {
super.viewDidLoad()
viewModel = StatusCodeViewModel()
viewModel.delegate = self
viewModel!.loadStatusCodes()
}
func callFinished() {
self.tableView.reloadData()
}
Don't forget to extend for delegate you just made:
class ViewController: UIViewController, StatusCodeViewModelDelegate {
Or, as #rmaddy suggested, in View model change loadStatusCodes to:
func loadStatusCodes(completion: #escaping () -> Void) {
apiClient.execute(service: .statusCode) { statusCodes in
self.statusCodes = statusCodes
}
}
Then, in the viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
viewModel = StatusCodeViewModel()
viewModel!.loadStatusCodes {
self.tableView.reloadData()
}
}
//This would do !
func loadStatusCodes(completion: #escaping () -> Void) {
apiClient.execute(service: .statusCode) { statusCodes in
self.statusCodes = statusCodes
completion()
}
}
// And in ViewController:
override func viewDidLoad() {
super.viewDidLoad()
viewModel = StatusCodeViewModel()
viewModel?.loadStatusCodes() {
self.tableView.reloadData()
}
}
My application gets stuck on the splash screen when running. It does already have a storyboard entry point which points to the view controller named studentsViewController so I don't know why it isn't working. There is no crash.
StudentsViewController:
import UIKit
class StudentsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var currentClass = VirtualRewardsClient.sharedInstance.getClass()
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
tableView.registerClass(StudentTableViewCell.self, forCellReuseIdentifier: "studentCell")
Class.sharedInstance.addStudent("Dhruv")
Class.sharedInstance.printClass()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Class.sharedInstance.students.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("studentCell") as StudentTableViewCell
return cell
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
VirtualRewardsClient.swift
import UIKit
class VirtualRewardsClient{
class var sharedInstance: VirtualRewardsClient{
struct Static{
static var instance = VirtualRewardsClient()
}
return Static.instance
}
func getClass() -> Class{
if let data = NSUserDefaults.standardUserDefaults().objectForKey(classKey) as? NSData{
let unarc = NSKeyedUnarchiver(forReadingWithData: data)
unarc.setClass(Class.self, forClassName: "Class")
let currentClass = unarc.decodeObjectForKey("root") as Class
Class.sharedInstance.students = currentClass.students
Class.sharedInstance.teacher = currentClass.teacher
return currentClass
}
return Class()
}
}
Class.swift
import Foundation
import UIKit
let classKey = "CLASS_KEY"
class Class: NSObject{
let defaults = NSUserDefaults.standardUserDefaults()
class var sharedInstance: Class{
struct Static{
static var instance: Class = VirtualRewardsClient.sharedInstance.getClass()
}
return Static.instance
}
var students:[Student] = [Student]()
var teacher = Teacher(currentClass: sharedInstance)
func addStudent(name: String, value: Int){
students.append(Student(name: name, startingPoints: value))
defaults.setObject(NSKeyedArchiver.archivedDataWithRootObject(Class.sharedInstance), forKey: classKey)
VirtualRewardsClient.sharedInstance.getClass()
}
func addStudent(name: String){
students.append(Student(name: name))
defaults.setObject(NSKeyedArchiver.archivedDataWithRootObject(Class.sharedInstance), forKey: classKey)
VirtualRewardsClient.sharedInstance.getClass()
}
func printClass(){
for i in students{
println("Student: \(i.name), Points: \(i.points)")
}
}
}