CoreData saving but not changing on additional button clicks - ios

I have an app Im trying to build with sleep data for sleep apnea. I successfully setup CoreData with some help and its saving the first user input from the textfields and displaying it in the log from CoreData (changing the strings to Doubles was a challenge but success). However, whenever I put another 2nd input in, it saves and displays the first input again. It won't change. What am I doing wrong? A side note, it also is not creating a unique ID for each entry for some reason. Thank you guys....you'd been awesome so far helping as Im a newbie.
SleepModel.swift
struct SleepModel: Identifiable, Hashable {
var id = UUID().uuidString // ID variable
var hoursSlept: Double // Hours slept variable
var ahiReading: Double // AHI variable
}
Save function in AddEntryView.swift which is called when the button is pushed
private func saveSleepModel() {
guard let hoursSleptDouble = Double(hoursSleptString) else {
print("hours slept is invalid")
return
}
guard let ahiReadingDouble = Double(ahiReadingString) else {
print("ahi reading is invalid")
return
}
// Creates Sleep Model for user input & sends parameters
var sleepModel = SleepModel(hoursSlept: hoursSleptDouble, ahiReading: ahiReadingDouble)
coreDataViewModel.saveRecord(sleepModel: sleepModel) {
print("Success")
}
}
CoreDataViewModel
import SwiftUI
import CoreData
class CoreDataViewModel: ObservableObject {
let container: NSPersistentContainer
#Published var savedRecords: [SleepEntity] = []
init() {
container = NSPersistentContainer(name: "SleepContainer")
container.loadPersistentStores { description, error in
if let error = error {
print("Error loading contaienr - \(error.localizedDescription)")
} else {
print("Core data loaded successfully")
}
}
fetchRecords()
}
func saveRecord(sleepModel: SleepModel, onSuccess: #escaping() -> Void) {
let newRecord = SleepEntity(context: container.viewContext)
newRecord.hoursSlept = sleepModel.hoursSlept
newRecord.ahiReading = sleepModel.ahiReading
saveData(onSuccess: onSuccess)
}
func saveData(onSuccess: #escaping() -> Void) {
do {
try container.viewContext.save()
fetchRecords()
onSuccess()
} catch let error {
print("Error saving items: \(error)")
}
}
func fetchRecords() {
let request = NSFetchRequest<SleepEntity>(entityName: "SleepEntity")
request.sortDescriptors = [
NSSortDescriptor(keyPath: \SleepEntity.id, ascending: false)
]
do {
savedRecords = try container.viewContext.fetch(request)
} catch let error {
print("Error fetching requests - \(error.localizedDescription)")
}
}
}

Related

How to get photo url from PhotosPicker in SwiftUI?

I am using PhotosPicker to let users select a photo. How do I retrieve the url of the selected photo?
I've tried printing out the imageSelection.itemIdentifier and got Optional("03966B05-1F51-4A20-801C-B617A2BC14DB/L0/001"), I don't know if this is related to a url path.
Here is my class using PhotosPicker, using sample code from WWDC2022.
class CreateViewModel: ObservableObject {
#Published var post: Post = Post() // Record being added to CloudKit
enum ImageState {
case empty, loading(Progress), success(Image), failure(Error)
}
#Published private(set) var imageState: ImageState = .empty
#Published var imageSelection: PhotosPickerItem? {
didSet {
if let imageSelection {
let progress = loadTransferable(from: imageSelection)
imageState = .loading(progress)
} else {
imageState = .empty
}
}
}
// Load asset data using transferable
private func loadTransferable(from imageSelection: PhotosPickerItem) -> Progress {
return imageSelection.loadTransferable(type: Image.self) { result in
DispatchQueue.main.async {
guard imageSelection == self.imageSelection else { return }
switch result {
case .success(let image?):
// Handle the success case with the image.
print("Image ID: \(imageSelection.itemIdentifier)") // Optional("03966B05-1F51-4A20-801C-B617A2BC14DB/L0/001")
self.imageState = .success(image)
case.success(nil):
// Handle the success case with an empty value.
self.imageState = .empty
case .failure(let error):
// Handle the failure case with the provided error.
print("Error image: \(error)")
self.imageState = .failure(error)
}
}
}
}
func createPost() async {
// 1. Create record object
let record = CKRecord(recordType: "Post")
// 2. Set record's fields
record.setValuesForKeys([
"title": post.title,
"caption": post.caption,
"likes": post.likes,
"size": post.keyboard.size.rawValue,
"keycaps": post.keyboard.keycaps,
"switches": post.keyboard.switches,
"case": post.keyboard.case,
"plate": post.keyboard.plate,
"foam": post.keyboard.foam
])
// Create CKAsset to store image onto CloudKit
let url = ...
record["image"] = CKAsset(fileURL: url) // Stuck! Don't know how to access url using PhotoPicker
// 3. Save to icloud (public database)
await savePost(record: record)
}
}

Runtime error when starting up iOS Firebase app

I never had this problem before but it all of a sudden occurred when I added deleting the user profiles in firebase user auth on my Swift UI app. What happens is when I start up the app a blank screen appears and after around 30 seconds a runtime error pops up in the terminal saying:
8.9.1 - [GoogleUtilities/AppDelegateSwizzler][I-SWZ] App Delegate does not conform to UIApplicationDelegate protocol.
I am fairly new to this so I am not sure what kinda code to even start providing since the app is kinda big but I hope what I provide is somewhat helpful.
This is my main view:
import SwiftUI
import Firebase
#main
struct ExampleApp: App {
init() {
FirebaseApp.configure()
}
var body: some Scene {
WindowGroup {
HomeView()
}
}
}
This is the class which manages user data:
class UsersManagerModel: ObservableObject {
// observed classes
#ObservedObject var fireViewModel = FirebaseViewModel()
// shared info
#AppStorage("email") var email = ""
// variable used for user info
#Published var firstName = ""
#Published var lastName = ""
func deleateProfile() {
// remove user from storage
let db = Firestore.firestore()
let docRef = db.collection("Users").document("\(email)")
docRef.delete()
// remove user from firebase auth
if let user = Auth.auth().currentUser {
user.delete { error in
if let error = error {
print("error \(error)")
} else {
print("success")
self.fireViewModel.loginPresented.toggle()
}
}
}
}
func fetchUserData() {
let db = Firestore.firestore()
let docRef = db.collection("Users").document("\(email)")
docRef.getDocument { (document, error) in
guard error == nil else {
print("error", error ?? "")
return
}
if let document = document, document.exists {
let data = document.data()
if let data = data {
print("data", data)
self.firstName = data["FirstName"] as? String ?? ""
}
}
}
}
}
I really appreciate any help I can get. Thanks. Once again the runtime error is:
8.9.1 - [GoogleUtilities/AppDelegateSwizzler][I-SWZ] App Delegate does not conform to UIApplicationDelegate protocol.

Fetch CoreData using Generic Model and privateQueueConcurrencyType

I've made a method to fetch my coredata Objects array using generics and privateQueueConcurrencyType. However, I'm doing something wrong somewhere and I'm experiencing crashes because of that.
The place where I get the crash as follow. what am I missing here ??
func fetchData<T: NSFetchRequestResult>(entity: String, model: T.Type, _ custom_predicate: NSPredicate?=nil) throws -> [T] {
let request = NSFetchRequest<T>(entityName: entity)
if custom_predicate != nil {
request.predicate = custom_predicate
}
request.returnsObjectsAsFaults = false
//Crash is the bellow line
return try privateMOC.fetch(request)//This line throws the crash
}
My privateMOC initialisation as follow.
class StorageManager: NSObject {
let privateMOC: NSManagedObjectContext!
private override init() {
privateMOC = CoreDataManager.sharedManager.updateContext
}
private static var SMInstance: StorageManager?
lazy var managedObjectContext: NSManagedObjectContext = {
return CoreDataManager.sharedManager.persistentContainer.viewContext
}()
}
My CoreData stack as follow.
class CoreDataManager {
static let sharedManager = CoreDataManager()
let persistentContainer: NSPersistentContainer!
let viewContext: NSManagedObjectContext!
let updateContext: NSManagedObjectContext!
private init() {
let container: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Store")
container.loadPersistentStores(completionHandler: { (_, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
self.persistentContainer = container
self.viewContext = persistentContainer.viewContext
//This is where I use the privateQueueConcurrencyType formy privateMOC
let _updateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
_updateContext.parent = self.viewContext
self.updateContext = _updateContext
}
Stacktrace as follow.
When you are using background context (private queue), you should wrap it in perform or performAndWait. With that said the fetch method should be called like this:
context.performAndWait {
context.fetch(request)
}
Because the queue is private and internal to the NSManagedObjectContext instance, it can only be accessed through the perform(:) and the performAndWait(:) methods.
More about using core data in background read here
Edit 1:
performAndWait takes a closure as its parameter that has no return value, so you can't return result/value from it. You need to understand these concepts in Swift.
Let's take your problem for example, you have a function that you want to return array of some values after the fetch request is performed. What you did is not going to work because of what I said earlier, so we will "extract" the context outside of the function.
func fetch<T>(_ type: T.Type, in context: NSManagedObjectContext) -> [T] {
return context.fetch(request)
}
In that way we can wrap the function in whatever context we want either background or viewContext(main).
context.performAndWait {
let products = fetch(SubProduct.self, in: context)
// do some other stuff
}
I can't replicate your crash but I was getting an Error in the line you highlighted.
I didn't have info on you SubProduct so I used the generic Item that comes with Xcode Projects
There are a ton of comments that come with the code.
This is just a standard View to test functionality.
struct CoreDataBackgroundView: View {
#StateObject var vm: CoreDataBackgroundViewModel2 = CoreDataBackgroundViewModel2()
public init(){}
var body: some View {
VStack{
List(vm.items){ item in
Button(action: {
vm.fetchItem(timestamp: item.timestamp)
}, label: {
Text(item.timestamp!.description)
})
}
Text("fetched Item = \(vm.item?.timestamp?.description ?? "nil")")
Text(vm.items.count.description)
Button("fetch", action: {
vm.fetchItems()
})
Button("add", action: {
vm.addItem()
})
}
}
}
struct CoreDataBackgroundView_Previews: PreviewProvider {
static var previews: some View {
CoreDataBackgroundView()
}
}
This is how I incorporated your code
class CoreDataBackgroundViewModel2: ObservableObject{
private let manager = StorageManager.SMInstance
#Published var items: [Item] = []
#Published var item: Item? = nil
///Fetch everything
func fetchItems() {
do{
items = try manager.fetchData(entity: "Item", model: Item.self)
}catch{
print(error)
items = []
}
}
///This way you can just fetch the item(s) that you need
func fetchItem(timestamp: Date?) {
if timestamp != nil{
do{
item = try manager.fetchData(entity: "Item", model: Item.self, NSPredicate(format: "timestamp == %#", timestamp! as CVarArg)).first
}catch{
print(error)
item = nil
}
}else{
item = nil
}
}
func addItem() {
manager.addItem()
}
}
//Something had to change here there is no entry
class StorageManager: NSObject {
let manager = CoreDataManager.sharedManager
let privateMOC: NSManagedObjectContext
private override init() {
privateMOC = manager.container.newBackgroundContext()
}
//I made it a singleton
static var SMInstance: StorageManager = StorageManager()
lazy var managedObjectContext: NSManagedObjectContext = {
return manager.container.viewContext
}()
func fetchData<T: NSFetchRequestResult>(entity: String, model: T.Type, _ custom_predicate: NSPredicate?=nil) throws -> [T] {
let request = NSFetchRequest<T>(entityName: entity)
if custom_predicate != nil {
request.predicate = custom_predicate
}
request.returnsObjectsAsFaults = false
//Didn't get a crash but an Error
//Value of optional type 'NSManagedObjectContext?' must be unwrapped to refer to member 'fetch' of wrapped base type 'NSManagedObjectContext'
// It is bad practice to use ! try to minimize those as much as possible
return try privateMOC.fetch(request)
}
func addItem()
{
privateMOC.perform {
let newItem = Item(context: self.privateMOC)
newItem.timestamp = Date()
newItem.text = "sample"
do{
try self.privateMOC.save()
}catch{
print(error)
}
}
}
}
//I changed your manager because there were a bunch of nil objects that are unnecessary also, I added the ability to use an inMemory conxtext for when you are using previews/canvas
//This is basically the standard setup with comes with all New Projects in Xcode
//Some of the errors probably had to do with the let variables that were after loadPersistentStores. They were called before the store was done loading so they stayed nil. The speed of the load might have given you mixed results.
class CoreDataManager {
static let sharedManager = CoreDataManager.previewAware()
private static func previewAware() -> CoreDataManager{
if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"{
return CoreDataManager.preview
}else{
return CoreDataManager.shared
}
}
private static let shared = CoreDataManager()
private static var preview: CoreDataManager = {
let result = CoreDataManager(inMemory: true)
let viewContext = result.container.viewContext
for n in 0..<2 {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
}
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
//This is usually the AppName
container = NSPersistentContainer(name: "AppName")
//If you are in Preview the setup work take place only on a device or simulator
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = false
container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyStoreTrump
}
}

Subscribe a single observable inside another single creation rxswift

I want to get data from server and update my DB after that I'll show received data to the user. For this goal I have a method(getData()) in my view model that returns a Single I call and subscribe this method in the view controller(myVC.getData.subscribe({single in ...})) in this method at first I call and subscribe(#1)(getUnread()->Single) the method run but I can not get the single event, I can not understand why I can't get the event(#3) in call back(#4)
after that I want to save data with calling(#2)(save([Moddel])->single)
//I removed some part of this code it was to big
//This method is View Model
func getData() -> Single<[Model]> {
return Single<[Model]>.create {[weak self] single in
//#1
self!.restRepo.getUnread().subscribe({ [weak self] event in
//#4
switch event {
case .success(let response):
let models = response
//#2
self!.dbRepo.save(issues!).subscribe({ event in
switch event {
case .success(let response):
let models = response
single(.success(models))
case .error(let error):
single(.error(error))
}
}).disposed(by: self!.disposeBag)
case .error(let error):
single(.error(error))
}
}).disposed(by: self!.disposeBag)
return Disposables.create()
}
}
.
.
//I removed some part of this code it was to big
//This method is in RestRepo class
func getUnread() -> Single<[Model]> {
return Single<[Model]>.create { single in
let urlComponent = ApiHelper.instance.dolphinURLComponents(for: ApiHelper.ISSUES_PATH)
var urlRequest = URLRequest(url: urlComponent.url!)
ApiHelper.instance.alamofire.request(urlRequest).intercept().responseJSON { response in
debugPrint(response)
let statusCode = response.response?.statusCode
switch statusCode {
case 200:
do {
let models = try JSONDecoder().decode([Model].self, from: response.data!)
//#3
single(.success(models))
}catch{
print(error)
}
case 304:
debugPrint(response)
default:
single(.error(IssueResponseStatusCodeError(code: statusCode ?? 0)))
}
}
return Disposables.create()
}
First you need to change your thinking. You don't do anything in the app. At best, you lay out the Observable chains (which don't do anything anymore than water pipes "do" something.) Then you start the app and let the "water" flow.
So with that in mind, let's examine your question:
I want to get data from server...
It's not that "you" want to get the data. The request is made as a result of some action (probably a button tap) by the user or by some other side effect. What action is that? That needs to be expressed in the code. For the following I will assume it's a button tap. That means you should have:
class Example: UIViewController {
var button: UIButton!
var restRepo: RestRepo!
override func viewDidLoad() {
super.viewDidLoad()
let serverResponse = button.rx.tap
.flatMapLatest { [restRepo] in
restRepo!.getUnread()
.map { Result<[Model], Error>.success($0) }
.catchError { .just(Result<[Model], Error>.failure($0)) }
}
.share(replay: 1)
}
}
protocol RestRepo {
func getUnread() -> Observable<[Model]>
}
struct ProductionRestRepo: RestRepo {
func getUnread() -> Observable<[Model]> {
let urlComponent = ApiHelper.instance.dolphinURLComponents(for: ApiHelper.ISSUES_PATH)
let urlRequest = URLRequest(url: urlComponent.url!)
return URLSession.shared.rx.data(request: urlRequest)
.map { try JSONDecoder().decode([Model].self, from: $0) }
}
}
class ApiHelper {
static let ISSUES_PATH = ""
static let instance = ApiHelper()
func dolphinURLComponents(for: String) -> URLComponents { fatalError() }
}
struct Model: Decodable { }
The thing to notice here is that getUnread() is an effect that is caused by button.rx.tap. The above establishes a cause-effect chain.
Your question goes on to say "you" want to:
... update my DB...
Here, the cause is the network request and the effect is the DB save so we simply need to add this to the viewDidLoad (note that the code below uses RxEnumKit.):
let dbResponse = serverResponse
.capture(case: Result.success)
.flatMapLatest { [dbRepo] models in
dbRepo!.save(models)
.map { Result<Void, Error>.success(()) }
.catchError { .just(Result<Void, Error>.failure($0)) }
}
Your question also says that "you" want to:
... show received data to the user.
Note here that showing the received data to the user has nothing to do with the DB save. They are two independent operations that can be done in parallel.
Showing the received data to the user has the serverResponse as the cause, and the showing as the effect.
serverResponse
.capture(case: Result.success)
.subscribe(onNext: { models in
print("display the data to the user.", models)
})
.disposed(by: disposeBag)
Lastly, you don't mention it, but you also have to handle the errors:
So add this to the viewDidLoad as well:
Observable.merge(serverResponse.capture(case: Result.failure), dbResponse.capture(case: Result.failure))
.subscribe(onNext: { error in
print("an error occured:", error)
})
.disposed(by: disposeBag)
The code below is all of the above as a single block. This compiles fine...
import UIKit
import RxSwift
import RxCocoa
import EnumKit
import RxEnumKit
extension Result: CaseAccessible { }
class Example: UIViewController {
var button: UIButton!
var restRepo: RestRepo!
var dbRepo: DBRepo!
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
let serverResponse = button.rx.tap
.flatMapLatest { [restRepo] in
restRepo!.getUnread()
.map { Result<[Model], Error>.success($0) }
.catchError { .just(Result<[Model], Error>.failure($0)) }
}
.share(replay: 1)
let dbResponse = serverResponse
.capture(case: Result.success)
.flatMapLatest { [dbRepo] models in
dbRepo!.save(models)
.map { Result<Void, Error>.success(()) }
.catchError { .just(Result<Void, Error>.failure($0)) }
}
serverResponse
.capture(case: Result.success)
.subscribe(onNext: { models in
print("display the data to the user.", models)
})
.disposed(by: disposeBag)
Observable.merge(serverResponse.capture(case: Result.failure), dbResponse.capture(case: Result.failure))
.subscribe(onNext: { error in
print("an error occured:", error)
})
.disposed(by: disposeBag)
}
}
protocol RestRepo {
func getUnread() -> Observable<[Model]>
}
protocol DBRepo {
func save(_ models: [Model]) -> Observable<Void>
}
struct ProductionRestRepo: RestRepo {
func getUnread() -> Observable<[Model]> {
let urlComponent = ApiHelper.instance.dolphinURLComponents(for: ApiHelper.ISSUES_PATH)
let urlRequest = URLRequest(url: urlComponent.url!)
return URLSession.shared.rx.data(request: urlRequest)
.map { try JSONDecoder().decode([Model].self, from: $0) }
}
}
class ApiHelper {
static let ISSUES_PATH = ""
static let instance = ApiHelper()
func dolphinURLComponents(for: String) -> URLComponents { fatalError() }
}
struct Model: Decodable { }
I hope all this helps you, or at least generates more questions.

Can't access model object data outside the alamofire request scope while populating api data on badoo/Chatto chat text

Previously I successfully access model objects anywhere in the class but while populating data on badoo/chatto text view I am stuck.
I am integrating chat message api into badoo chat view
Basically, the issue is alamofire response is not getting outside of the scope.
Did I try with compilation handler but no luck? Is there any way to resolve this issue?
Thanks in advance .
Here is code snippet :
import Foundation
import Chatto
import ChattoAdditions
import SwiftyJSON
import Alamofire
class DemoChatMessageFactory {
public static var chats = [ChatModel]()
class func makeMessage(_ uid:String) -> DemoTextMessageModel{
print("uid makeMessage : \(uid)")
return self.makeMessageData(uid, isIncoming:false)
}
class func makeMessageData(_ uid: String,isIncoming:Bool) -> DemoTextMessageModel {
if isIncoming == true{
return self.makeTextFinalMessage(uid, isIncoming:isIncoming)
} else {
return self.makeTextFinalMessage(uid, isIncoming: isIncoming)
}
}
public class func makeTextMessage(_ uid: String, isIncoming: Bool,text:String) -> DemoTextMessageModel {
let messageModel = self.makeMessageModel(uid, isIncoming: isIncoming,
type: TextMessageModel<MessageModel>.chatItemType)
let textMessageModel = DemoTextMessageModel(messageModel:messageModel,
text: text)
return textMessageModel
}
public class func makeTextFinalMessage(_ uid: String, isIncoming: Bool) -> DemoTextMessageModel {
var text = String()
var uidInt = Int(uid)
print("string uid 121 \(uid)")
print("print is Incomming data or not 1: \(isIncoming)")
print("uid count :\(uid.count)")
let urlString = "[My message Api]"
Alamofire.request(urlString, method: .get).validate().responseJSON {
(response) -> Void in
if let value = response.data {
do {
let json = try JSON(data: value)
if let dictionnary = json.dictionaryObject {
if let messageArray = dictionnary["message"] as?[[String: Any]] {
self.chats.removeAll()
for arr in messageArray {
self.chats.append(ChatModel(ChatListJSON: arr))
}
}
}
} catch {
print("cannot convert to Json")
}
}
print("print int 122 : \(uidInt!)")
print("Chat List Id DemoChatMessageFactory \(self.chats[uidInt!].chatId)")
print("chat message: \(String(describing: uidInt!)) th \(self.chats[uidInt!].chatMessage)")
self.textData = "\(self.chats[uidInt!].chatMessage)"
self.makeTextMessage(uid, isIncoming: isIncoming, text:self.textData) //Here I am bale to pass textData but ouside the Alamofire block can't access
}
//Here getting empty values
print("uid makeTextFinalMessage \(uid)")
print("in coming makeTextFinalMessage \(isIncoming)")
print("text makeTextFinalMessage \(text)")
//chat count also getting zero count
print("chat count final text\(chats.count)")
print("print chat count : \(self.chats.count)")
return self.makeTextMessage(uid, isIncoming: isIncoming, text:self.textData)
}
}
Test for completion handler
public var res: Any = ""
func getAllChatData(completionhandler:#escaping ([String: Any]?) -> ()){
let URL = "my api"
Alamofire.request(URL).responseJSON {
response in
if let json = response.result.value as? [String: Any] {
completionhandler(json, nil)
}
else if let error = response.result.error as Error? {
completionhandler(nil, error)
}
}
}
and call using like below inside the function
DemoChatMessageFactory.getAllChatData {
(result) in
res = result
print("response (res)")
}
please suggest me the proper way to alamofire with compilation handler
This is an example of converting all methods using the result of asynchronous call. As I have never used Chatto and you are not showing all the types in your code, so you may need to modify many parts of my code, but I believe you can see what you need to do with this code.
import Foundation
import Chatto
import ChattoAdditions
import SwiftyJSON
import Alamofire
class DemoChatMessageFactory {
public static var chats = [ChatModel]()
class func requestMessage(_ uid:String,
completion: #escaping (DemoTextMessageModel?, Error?)->Void) {
print("uid makeMessage : \(uid)")
self.requestMessageData(uid, isIncoming: false) { (model, error) in
completion(model, error)
}
}
class func requestMessageData(_ uid: String, isIncoming: Bool,
completion: #escaping (DemoTextMessageModel?, Error?)->Void) {
if isIncoming {
//...put any code needed when isIncoming is true
self.requestTextFinalMessage(uid, isIncoming: isIncoming) { model in
completion(model, error)
}
} else {
//...put any code needed when isIncoming is false
self.requestTextFinalMessage(uid, isIncoming: isIncoming) { model in
completion(model, error)
}
}
}
public class func makeTextMessage(_ uid: String, isIncoming: Bool, text: String) -> DemoTextMessageModel {
let messageModel = self.makeMessageModel(uid, isIncoming: isIncoming,
type: TextMessageModel<MessageModel>.chatItemType)
let textMessageModel = DemoTextMessageModel(messageModel:messageModel,
text: text)
return textMessageModel
}
public class func requestTextFinalMessage(_ uid: String, isIncoming: Bool,
completion: #escaping (DemoTextMessageModel?, Error?)->Void) {
var text = String()
var uidInt = Int(uid)
print("string uid 121 \(uid)")
print("print is Incomming data or not 1: \(isIncoming)")
print("uid count :\(uid.count)")
let urlString = "[My message Api]"
Alamofire.request(urlString, method: .get).validate().responseJSON {
(response) -> Void in
if let value = response.data {
do {
let json = try JSON(data: value)
if let dictionnary = json.dictionaryObject {
if let messageArray = dictionnary["message"] as?[[String: Any]] {
self.chats.removeAll()
for arr in messageArray {
self.chats.append(ChatModel(ChatListJSON: arr))
}
}
}
print("print int 122 : \(uidInt!)")
print("Chat List Id DemoChatMessageFactory \(self.chats[uidInt!].chatId)")
print("chat message: \(String(describing: uidInt!)) th \(self.chats[uidInt!].chatMessage)")
self.textData = "\(self.chats[uidInt!].chatMessage)"
completion(self.makeTextMessage(uid, isIncoming: isIncoming, text: self.textData), nil)
} catch {
print("cannot convert to Json")
completion(nil, error)
}
} else {
//better generate an error case result, and call completion.
//...
}
}
}
}
I changed some method names from make... to request... to show clarify they are asynchronous methods.
And the usage, if you intend to use your original code as:
let model = DemoChatMessageFactory.makeMessage(uid)
//Do some UI updates using `model`...
You may need to use asynchronous methods like:
DemoChatMessageFactory.requestMessage(uid) { (model, error) in
if let model = model {
//Do some UI updates using `model`...
} else {
//Do something for the error...
}
}

Resources