MVVM Async UIImage loading from network on iOS Swift - ios

I have been using MVC since my first app on iOS... now i want to try MVVM.
My approach is that a Model can contain the remote URL and the ViewModel makes the request to download the image. (pushing then to binded view)... I think this is suitable so as to avoid making a network request in Views (or even worse, cells!)
class Person: NSObject {
var firstName: String?
var lastName: String?
var avatarURL: URL?
}
class PersonEntryViewModel {
var name:String?
var avatarImage:UIImage?
init(person: Person?) {
super.init()
// omitted: binding self.name based on person.firstName & person.lastName
var request: URLRequest? = nil
if let avatarURL = person?.avatarURL {
request = URLRequest(url: avatarURL)
}
fetchImageFromNetwork({ response, data in
if let data = data {
avatarImage = UIImage(data: data)
}
})
}
}
What do you think?
My doubts are about memory. I could have a big array of viewmodels filled with UIImages...

If each cell is holding its own PersonEntryViewModel and that is getting discarded in the cell's prepare for reuse, then memory isn't a problem because images will be discarded at the same time.
I think it would be best not to put the download code in the init method. Better would be to have some sort of trigger method you can call when the view model is passed to the cell.
It really depends on what the larger architecture looks like.
If you want to see some examples of handling images in an Rx-MVVM-C app there are a couple of great ones in the RxSwift slack channel:
https://rxswift.slack.com/archives/CTSAM9V27/p1583148003073800
https://rxswift.slack.com/archives/CTSAM9V27/p1583149698079500
Join the discussion by going here: https://rxslack.herokuapp.com

Related

Saving object to be accessible anywhere

I have a condition hereby let's say a user has logged in by calling an API. And the response contain's user's details. Is there anyway we can save the details of the user as an object and be accessible globally? Below is how I used ObjectMapper to call the api
For the model class:
import ObjectMapper
class User: Mappable {
var id: Int?
var userId: Int?
var orgId: Int?
var type: Int?
var email: String?
var lang: String?
var nickname: String?
var status: Int?
var token: String?
required init(map: Map) {
mapping(map: map)
}
func mapping(map: Map) {
id <- map["id"]
userId <- map["userId"]
orgId <- map["orgId"]
type <- map["type"]
failedAttempt <- map["failedAttempt"]
email <- map["email"]
lang <- map["lang"]
nickname <- map["nickname"]
status <- map["status"]
token <- map["token"]
}
}
And from my Networking file,
func postLogin(params: [String: Any], controller: UIViewController, completion: #escaping (User) -> Void) {
alamofire("/login", method: .post, token: false, params: params, controller: controller) { result in
if let userDetails = Mapper<User>().map(JSON: result as! [String: Any]) {
DispatchQueue.main.async {
completion(userDetails)
}
}
}
}
Some solutions may be using UserDefaults but it's just not practical to be using 9 UserDefaults to save the 9 keys that we got from this response. What are the suggested ways of approach we can go about this where user logged in, we save these details as an object globally and even when after closing the app, the details are not reseted? Thank you all
I agree that saving 9 individual keys in UserDefaults is not practical. But why not encode the whole thing as JSON and save the resulting Data?
extension User: Codable { }
// Assuming user is an instance of User
guard let userJSON = try? JSONEncoder().encode(user) else {
// handle encoding failure
}
let userDefaultsKeyForUser = "com.yourdomain.snazzyapp.userInfo"
UserDefaults.standard.set(userJSON, forKey: userDefaultsKeyForUser)
I think Swift will automatically synthesize Codable conformance for your User class, but you might need to make it final. Or implement Codable explicitly.
To retrieve it
guard let userJSON = UserDefaults.standard.data(forKey: userDefaultsKeyForUser) else {
// Handle missing data (first run maybe?)
}
guard let user = try? JSONDecoder().decode(User.self, from: userJSON) else {
// Handle decoding failure
}
Alternative encoding/decoding methods
Although conforming to Codable would be the preferred way to do this, that can sometimes be a problem for classes, particularly when inheritance is involved. In particular Codable is a combination of Encodable and Decodable. Decodable is the one that is sometimes a problem for classes. The issue has to do with the required init(from decoder: Decoder) throws having to be declared directly in the class, not in an extension, and then you might have trouble with encoding the super. If your class is the base class that shouldn't be a problem. This is mainly a problem when you inherit from AppKit/UIKit classes.
If for some reason User can't conform to Codable, you can use NSCoding instead. It works a little differently.
In the worst case you could implement methods where you manually encode/decode each property explicitly to data. You could even store them as binary, but you'll probably need to do something like store byte counts for the strings so you know where each one starts and ends, and you'll need to come up with a scheme to indicate when they are nil in the encoding. It's not as flexible as JSON, which is why it's a last resort... although I will note that it is much faster. That wouldn't matter much for your User class, but I wrote a neural network library where I wanted to save the model at checkpoints during training, which meant encoding many layers of very large matrices. Millions of parameters. Reading and writing the model was about 20x faster with my own binary encoding than letting Codable handle it, even when I had Codable saving it as a binary plist.
A third option would be to re-work your User class. Either make it a struct, or use a struct internally to store its properties and use computed properties to get and set them in your class. All of your class's properties already conform to Codable, so Swift can definitely synthesize Codable conformance for a struct with those properties. If you still wrap the struct inside of a class, then you just have an initializer that takes a Data, and do the same decoding I showed above, to set the wrapped struct, and an encode method (or even a computed var) that encodes the struct as above and returns the Data.
I don't think you'll need these alternate solutions, but I mention them just in case I'm wrong, and because they're useful to know about for future situations.
You have to ask yourself how you would like to access this object with compilator autocompletion from different places in your app. Secondly, what is the life span of the object? The object should be accessible during login / logout, app session or app life time?
Let's start with the object life span.
login / logout, the object can be stored in memory
app session, the object can be stored in memory
app install / delete, the object should be stored in UserDefaults, Database or a file.
Autocompletion Driven Development aka ADD:
Deciding where to store userDetails object and how to access it
Let's say that you have a logic in a view controller which hide or unhide a view in a initial configuration defined in viewDidLoad. Write some prototype code to decide how you would like to access the 'userDetails' object from different places of the app.
func viewDidLoad() {
super.viewDidLoad()
if userDetails.status {
//userDetails is a global instance of a user object
}
if session.userDetails.status {
//session is a global instance of a Session object which has a property which stores a User instance in userDetails
}
if Session.current.userDetails.status {
// Session is a type and 'current' is a singleton of that type which has a userDetails property which stores an instance of User type
}
if User.userDetails.status {
// User is your type which can have a static property 'userDetails' which stores a User instance
}
}

Implementing VIPER architecture in iOS

I am implementing my project as per VIPER for the first time and I have some doubts regarding its implementation.This is what I have done so far:
1)Implement Login page
STEPS
i)User taps login button(on view controller).
ii)I have a request model where I store 'username' and 'password'.This is the structure of model:
struct Login{
struct Request{
var txt_email:String!
var txt_password:String!
}
struct Response {
var homeData:Dictionary<String,Any>
}
}
So I pass this Request object to the Interactor.
iii)In Interactor,I have different Workers(Worker class object methods) assigned to perform different tasks such as email validation,empty textFields validation etc.If all is well,the worker api method hits login API and passes the response back to Interactor via delegation.
iv)Update the 'Response' model in the above structure.
v)Now that I have the response in Interactor,I pass this response to the Presenter to do some manipulations according to what controller needs to display to the user.
vi)Pass the data to the Controller and it presents it to user.
Question 1:Am I doing everything right.If no , please show me the right way.If yes , please tell me if there is some room for improvement.
Question 2:I need to implement UITableView and UICollectionView on the home page and I think extensions is the way to go for them.I will follow the same strategy for Home page as well.But suppose , in 'didSelectRowAtIndexPath' , I need to show a pop up to user and I think it will be better for ViewController to ask Presenter directly about the data.But is it the correct way?If not what is the correct way?
Question 3:should I pass data from cellForRowAtIndexPath: to actual cell(MyCell:UITableViewCell) class methods and then assign values to UIElements?Yes or no?
Reference: https://medium.com/#javedmultani16/viper-architecture-viper-64f6cd91e6ec
We developer basically uses the MVC,MVP or MVVM architecture for the development as per the requirement. It is mattering which architecture you are choose to develop the application. Many factors affecting for selection of the software architecture like system designs, requirements, time-lines etc.
In Viper architecture, each block corresponds to an object with specific tasks, inputs and outputs. It is very similar to workers in an assembly line: once the worker completes its job on an object, the object is passed along to the next worker, until the product is finished.
V (View): View is responsible for the UI updates and show whatever the presenter tells it.
I (Interactor) : The Interactor is responsible for fetching data from the model layer, and its implementation is totally independent of the user interface.All the business logic written inside the Interactor. E.g. Get User Data API call written in the Interactor.
P (Presenter): Presenter performing role as intermediator it gets data from interaction and passes to View. (It may be data or any user action)
E (Entity): Basically it is contains the Object Model which is used by Interactor. E.g. Student,Friend,College etc.
R (Router): It contains navigation logic for the application. E.g. Next button action shows second screen.
Morever, I’ve use the PROTOCOL, which contains all the rules and work-flow for the particular module of the application. In iOS all the protocols written in the separate protocol swift file for each module.
Morever, I’ve use the PROTOCOL, which contains all the rules and work-flow for the particular module of the application. In iOS all the protocols written in the separate protocol swift file for each module.
Let’s see the file structure of it:
enter image description here
Benefits:
-All the modules are independent so VIPER is really good for large teams.
-It makes the source code cleaner, more compact and reusable
-It easier to adopt TDD (Test Driven Development)
-You can add easily new features to the existing application without changing other modules possibly.
-It can be applies SOLID principles.
-Reduced number of merge conflicts.
-It Makes it easy to write automated tests since your UI logic is separated from the business logic
struct Person { // Entity (usually more complex e.g. NSManagedObject)
let firstName: String
let lastName: String
}
struct GreetingData { // Transport data structure (not Entity)
let greeting: String
let subject: String
}
protocol GreetingProvider {
func provideGreetingData()
}
protocol GreetingOutput: class {
func receiveGreetingData(greetingData: GreetingData)
}
class GreetingInteractor : GreetingProvider {
weak var output: GreetingOutput!
func provideGreetingData() {
let person = Person(firstName: "David", lastName: "Blaine") // usually comes from data access layer
let subject = person.firstName + " " + person.lastName
let greeting = GreetingData(greeting: "Hello", subject: subject)
self.output.receiveGreetingData(greeting)
}
}
protocol GreetingViewEventHandler {
func didTapShowGreetingButton()
}
protocol GreetingView: class {
func setGreeting(greeting: String)
}
class GreetingPresenter : GreetingOutput, GreetingViewEventHandler {
weak var view: GreetingView!
var greetingProvider: GreetingProvider!
func didTapShowGreetingButton() {
self.greetingProvider.provideGreetingData()
}
func receiveGreetingData(greetingData: GreetingData) {
let greeting = greetingData.greeting + " " + greetingData.subject
self.view.setGreeting(greeting)
}
}
class GreetingViewController : UIViewController, GreetingView {
var eventHandler: GreetingViewEventHandler!
let showGreetingButton = UIButton()
let greetingLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents:.TouchUpInside)
}
func didTapButton(button: UIButton) {
self.eventHandler.didTapShowGreetingButton()
}
func setGreeting(greeting: String) {
self.greetingLabel.text = greeting
}
// layout code goes here
}
// Assembling of VIPER module, without Router
let view = GreetingViewController()
let presenter = GreetingPresenter()
let interactor = GreetingInteractor()
view.eventHandler = presenter
presenter.view = view
presenter.greetingProvider = interactor
interactor.output = presenter
Regarding Question 2 and 3, here's answer with my simple example of Viper app using UITableView: https://stackoverflow.com/a/45709546/3990005.
The ViewController does not speak to the Interactor, but to presenter, so on cellForRowAtIndexPath: you should call the Presenter with information, which IndexPath was selected.
Regarding cell creation, what I like to do is have a setup method in UITableViewCell class, and in ViewController ask Presenter for data and pass it to the cell. Some of it is visible in the above example, and here's cell's setupCell method:
func setupCell(withSong: song) {
titleLabel.text = song.title
descriptionLabel.text = song.description
}

Use core data in swift 2 without using tables

I'm learning to be an iOS app developer and I want to make an app which stores core data. I know how to do it using tables but is there a way I can store data without using tables? Like I'm trying to make an app which saves about a 100 different variables but m not using tables in it. Can someone please direct me to a full tutorial of how it's done? I came across one tutorial on Ray weindervich but it was done on swift 1.2 and it didn't work for me. Thanks
Core data depends on entities, now something that might help to share (probably you knew this already) is that Core data entity is not a table it represents a thing that can be identify and quantify for example a fruit regardless what your back end is; with that been said, now I have a question, when you say table do do you mean the entities or an actual database table? If you mean entity, with Core data you can say use SQLite as backend or xml file as back end but regardless how you store the data you will need to create at least one entity.
What was suggested in the comments still using entities. So my suggestion would be just create one entity using one of this options:
1. Entity
variable1 datatype
variable2 datatype
...
...
variable n datatype
or
2. Entity
key String
Value object
With option one you will have to know all the possible variables that your application will use and one good advantage is that you won't have to do down casting neither unwrapping.
With option two you don't need to know all the possible variables and also your data can grow without changing the app, the only downside is you will have to wrap and unwrap the data from each record.
These are my suggestions for you.
Hope this help
UPDATE :
So here are the steps that I think you need to follow to achieve your request (important: this a simple sample):
Make sure your project has enable since creation Core Data.
In the Model add the Entity as the picture shows:
Then add the attributes as the picture shows:
Add the add the subclass; this part is not mandatory but makes it easy to handle each entity and its properties.
Then you should have something similar to the following code for the entity:
import Foundation
import CoreData
class Generic: NSManagedObject {
#NSManaged var key: String?
#NSManaged var value: NSObject?
}
And your view controller should have something like this in order to read and save the data:
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet var txtVariable: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context = delegate.managedObjectContext
let request = NSFetchRequest(entityName: "Generic")
let filter = NSPredicate(format: "key = %#", "xyz")
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [filter])
do {
let records = try context.executeFetchRequest(request) as! [Generic]
if (records.count>0)
{
txtVariable.text = (records[0].value as! String)
}
}
catch let error as NSError{
NSLog(error.localizedDescription)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func btnSave_Click(sender: AnyObject) {
let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context = delegate.managedObjectContext
let record = NSEntityDescription.insertNewObjectForEntityForName("Generic", inManagedObjectContext: context) as? Generic
record?.key = "xyz"
record?.value = txtVariable.text
do {
try context.save()
}
catch let error as NSError{
NSLog(error.localizedDescription)
}
}}

Use Realm with Collection View Data Source Best Practise

I'll make it short as possible.
I have an API request that I fetch data from (i.e. Parse).
When I'm getting the results I'm writing it to Realm and then adding them to a UICollectionView's data source.
There are requests that take a bit more time, which run asynchronous. I'm getting the needed results after the data source and collection view was already reloaded.
I'm writing the needed update from the results to my Realm database.
I have read that it's possible to use Realm's Results. But I honestly didn't understood it. I guess there is a dynamic and safe way working with collection views and Realm. Here is my approach for now.
This is how I populate the collection view's data source at the moment:
Declaration
var dataSource = [Realm_item]()
where Realm_item is a Realm Object type.
Looping and Writing
override func viewDidLoad() {
super.viewDidLoad()
for nowResult in FetchedResultsFromAPI
{
let item = Realm_item()
item.item_Title = nowResult["Title"] as! String
item.item_Price = nowResult["Price"] as! String
// Example - Will write it later after the collectionView Done - Async request
GetFileFromImageAndThanWriteRealm(x.image)
// Example - Will write it later after the collectionView Done - Async request
dataSource.append(item)
}
//After finish running over the results *Before writing the image data*
try! self.realm.write {
self.realm.add(self.dataSource)
}
myCollectionView.reloadData()
}
After I write the image to Realm to an already created "object". Will the same Realm Object (with the same primary key) automatically update over in the data source?
What is the right way to update the object from the data source after I wrote the update to same object from the Realm DB?
Update
Model class
class Realm_item: Object {
dynamic var item_ID : String!
dynamic var item_Title : String!
dynamic var item_Price : String!
dynamic var imgPath : String?
override class func primaryKey() -> String {
return "item_ID"
}
}
First I'm checking whether the "object id" exists in the Realm. If it does, I fetch the object from Realm and append it to the data source. If it doesn't exist, I create a new Realm object, write it and than appending it.
Fetching the data from Parse
This happens in the viewDidLoad method and prepares the data source:
var query = PFQuery(className:"Realm_item")
query.limit = 100
query.findObjectsInBackgroundWithBlock { (respond, error) -> Void in
if error == nil
{
for x in respond!
{
if let FetchedItem = self.realm.objectForPrimaryKey(Realm_item.self, key: x.objectId!)
{
self.dataSource.append(FetchedItem)
}
else
{
let item = Realm_item()
item.item_ID = x.objectId
item.item_Title = x["Title"] as! String
item.item_Price = x["Price"] as! String
let file = x["Images"] as! PFFile
RealmHelper().getAndSaveImageFromPFFile(file, named: x.objectId!)
self.dataSource.append(item)
}
}
try! self.realm.write {
self.realm.add(self.dataSource)
}
self.myCollectionView.reloadData()
print(respond?.count)
}
}
Thank you!
You seem to have a few questions and problems here, so I'll do my best.
I suggest you use the Results type as your data source, something like:
var dataSource: Results<Realm_item>?
Then, in your viewDidLoad():
dataSource = realm.objects(Realm_item).
Be sure to use the relevant error checking before using dataSource. We use an optional Results<Realm_item> because the Realm object you're using it from needs to be initialised first. I.e., you'll get something like "Instance member * cannot be used on type *" if you try declaring the results like let dataSource = realm.objects(Realm_item).
The Realm documentation (a very well-written and useful reference to have when you're using Realm as beginner like myself), has this to say about Results...
Results are live, auto-updating views into the underlying data, which means results never have to be re-fetched. Modifying objects that affect the query will be reflected in the results immediately.
Your mileage may vary depending on how you have everything set up. You could try posting your Realm models and Parse-related code for review and comment.
Your last question:
What is the right way to update the "object" from the Data Source after i wrote the update to same object from the Realm DB?
I gather you're asking the best way to update your UI (CollectionView) when the underlying data has been updated? If so...
You can subscribe to Realm notifications to know when Realm data is updated, indicating when your app’s UI should be refreshed for example, without having to re-fetch your Results.

How to use Core Data synchronously?

I'm trying to make an iOS 8 App with Swift and i need to download data from JSON and save it , but i don't understand Core Data mechanism. (I'm coming from Android with ORM Lite and Windows Phone with sqlite-net).
I'm trying to make two tasks, "GetAllNewsTask" returning all News from database , and "UpdateAllNewsTask" downloading JSON and parsing it, save to database and return all News.
The function getEntitiesFromJson transform parsed JSON string to entity object
class func getEntitiesFromJson(json: JSONValue) -> [NewsEntity]?{
var rList : [NewsEntity] = []
var array = json.array
var countItr = array?.count ?? 0
if(array == nil){
return nil
}
if(countItr > 0){
for index in 0...countItr-1{
var news = NewsEntity()
var jsonVal = array?[index]
news.id = jsonVal?["id"].integer ?? 0
........
rList.append(news)
}
}
return rList
}
GetAllNewsTask (newsDao.findAll() currently return an harcoded empty array, i didn't found how to select all NewsEntity synchronously)
class GetAllNewsTask:NSOperation {
var result : Array<News>?
override func main() -> (){
result = executeSync()
}
func executeSync() -> Array<News>? {
let newsDao = NewsDAO()
let entities = newsDao.findAll()
return NewsModel.getVOsFromEntities(entities)
}
UpdateAllNewsTask
class UpdateAllNewsTask:NSOperation {
var result : Array<News>?
override func main() -> (){
result = executeSync()
}
func executeSync() -> Array<News>? {
let response = JsonServices.getAllNews()
var managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext!
var entityDescription = NSEntityDescription.entityForName("NewsEntity", inManagedObjectContext: managedObjectContext)
var entities = NewsModel.getEntitiesFromJson(response)
//TODO insert new, update existing and remove old
return GetAllNewsTask().executeSync()
}
I'm trying to add or update all NewsEntity and delete old, in Java i used List.removeAll(Collection<T>) but i can't found how to do this in Swift.
I got an exception when i override equals and hashcode in NewsEntity class.
Before continuing, is it the correct way to do this ?
If yes there is any good tutorial which demonstrate how to do this?
If no what is the correct way ?
Typically Core Data transactions should always be performed on the object's Managed Object Context thread. For this reason you will see the performBlock and performBlockAndWait calls in NSManagedObjectContext.
Since you are using the main thread you are technically synchronous assuming you are making those update calls on the main thread. If you are not then I would suggest wrapping your synch call into a performBlockAndWait call.
That being said, you should leverage Apple's Documentation on the subject as they explain how you can implement multithreaded core data. You should always perform your server related updates on a background thread.
If you want to implement a removeAll feature you will need to manually fetch all the objects you want to remove and call context.deleteObject(managedObject). Alternatively if you want something more powerful that should enforce cascade deletion, you can set this in your model editor when you select the relationship. The following Delete Rules are available:
Nullify
Cascade
No Action
Deny
Finally, you might find this post useful in explaining some of the commonly used Core Data stack setups and the various performance of each.
Welcome to iOS and good luck:)
EDIT
As an aside you might find Ray Wenderlich provides some great Core Data Tutorials

Resources