SwiftUI ViewModel how to initialized empty single struct? - ios

In my ViewModel I'm fetching data from API and I want to populate my variable with the data, however when I declare the variable, it returns error.
ViewModel.swift
class PromoDetailViewModel: ObservableObject, ModelService {
var apiSession: APIService
#Published var dataArray = [BannerDetailResData]() // This is okay
#Published var data = BannerDetailResData // This returns error
// Error message is:
// Expected member name or constructor call after type name
// Add arguments after the type to construct a value of the type
// Use .self to reference the type object
init(apiSession: APIService = APISession()) {
self.apiSession = apiSession
}
func getPromoDetail() {
let cancellable = self.getBannerDetail(bannerID: bannerID)
.sink(receiveCompletion: { result in
switch result {
case .failure(let error):
print("Handle error: \(error)")
case .finished:
break
}
}) { (result) in
if result.statusCode == 200 {
self.data = result.data
}
self.isLoading = false
}
cancellables.insert(cancellable)
}
}
BannerDetailResData.swift
struct BannerDetailResData: Codable, Hashable {
let bannerId: String
let bannerImg: String
let startDate: String
let endDate: String
}
Why is it when I declare as BannerDetailResData, it works perfectly? What is the correct way of declaring this single struct object? Thank you in advance

Make it optional
#Published var data:BannerDetailResData?

Related

Map array of objects into another array with Combine not working?

I am subscribing to a #Published array in my view model so I can .map every object appended as an array of PostAnnotations...
I am not able to map the post array into an array of PostAnnotations and get the error:
ERROR MESSAGE: Declared closure result '()' is incompatible
What am I doing wrong??
class UserViewModel: ObservableObject {
var subscriptions = Set<AnyCancellable>()
let newPostAnnotationPublisher = PassthroughSubject<[PostAnnotation], Never>()
#Published var currentUsersPosts: [Posts] = []
func addCurrentUsersPostsSubscriber() {
$currentUsersPosts
// convert each post into a PostAnnotation
.map { posts -> [PostAnnotation] in
// ^ERROR MESSAGE: Declared closure result '()' is incompatible
//with contextual type '[SpotAnnotation]'
posts.forEach { post in
let postAnnotation = PostAnnotation(post: post)
return postAnnotation
}
}
.sink { [weak self] postAnnotations in
guard let self = self else { return }
// send the array of posts to all subscribers to process
self.newPostAnnotationsPublisher.send(postAnnotations)
}
.store(in: &subscriptions)
}
func loadCurrentUsersPosts() {
PostApi.loadCurrentUsersPosts { response in
switch response {
case .success(let posts):
self.currentUsersPosts.append(contentsOf: spots)
case .failure(let error):
//////
}
}
}
}
forEach doesn't return a value. You want map, which returns a new collection based on the transform performed inside the closure:
.map { posts -> [PostAnnotation] in
posts.map { post in
let postAnnotation = PostAnnotation(post: post)
return postAnnotation
}
}
Or, even shorter:
.map { posts -> [PostAnnotation] in
posts.map(PostAnnotation.init)
}
And, even shorter (although, I'd argue that it starts losing readability at this point):
.map { $0.map(PostAnnotation.init) }

Why the swift compiler cannot use my subscript?

I have code that resembles this
I created custom subscript to do the unwrapping for me to make things easier.
enum MyEnum {
case one
case two
case three
}
struct MyStruct {
static var empty: Self {
return .init()
}
//Some Variables Here
}
class MyClass {
var index = 0
var data: [MyEnum: MyStruct] = [
.two: .empty,
.three: .empty,
.one: .empty
]
var allTypes: [MyEnum] {
switch Bool.random() {
case true:
return [.two, .three]
case false:
return [.one]
}
}
var currentSelectedType: MyEnum {
return allTypes[index]
}
func myMethod(type: MyEnum) {
let one: MyStruct = data[type]!
let two: MyStruct = data[currentSelectedType]!
let three: MyStruct = data[allTypes[index]]
let four: MyStruct = data[.one]
}
}
fileprivate extension Dictionary {
subscript(_ key: Key) -> Value where Key == MyEnum, Value == MyStruct {
get {
return self[key]!
}
set {
self[key] = newValue
}
}
}
in my MyClass myMethod why I have to forcely unwrapp one and two but not three and four otherwise I get a compile time error
let one: MyStruct = data[type] // Error Value of optional type 'MyStruct?' must be unwrapped to a value of type 'MyStruct'
let two: MyStruct = data[currentSelectedType] //Error Value of optional type 'MyStruct?' must be unwrapped to a value of type 'MyStruct'
Is there something I'm missing here?
I don't have an answer on why compiler isn't picking the expected overload in this situation.
I would recommend clarifying the overload you wish to use at call site, like following.
fileprivate extension Dictionary {
subscript(safe key: Key, defaultValue: Value = .empty) -> Value where Key == MyEnum, Value == MyStruct {
get { return self[key, default: defaultValue] }
set { self[key] = newValue }
}
}
With above, you can tell compiler explicitly to use your preferred overload.
func myMethod(type: MyEnum) {
let one: MyStruct = data[safe: type]
let two: MyStruct = data[safe: currentSelectedType]
let three: MyStruct = data[safe: allTypes[index]]
let four: MyStruct = data[safe: .one]
}

Escaping closure captures mutating 'self' parameter, Firebase

I have the following code, How can i accomplish this without changing struct into class. Escaping closure captures mutating 'self' parameter,
struct RegisterView:View {
var names = [String]()
private func LoadPerson(){
FirebaseManager.fetchNames(success:{(person) in
guard let name = person.name else {return}
self.names = name //here is the error
}){(error) in
print("Error: \(error)")
}
init(){
LoadPerson()
}a
var body:some View{
//ui code
}
}
Firebasemanager.swift
struct FirebaseManager {
func fetchPerson(
success: #escaping (Person) -> (),
failure: #escaping (String) -> ()
) {
Database.database().reference().child("Person")
.observe(.value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: Any] {
success(Person(dictionary: dictionary))
}
}) { (error) in
failure(error.localizedDescription)
}
}
}
SwiftUI view can be created (recreated) / copied many times during rendering cycle, so View.init is not appropriate place to load some external data. Use instead dedicated view model class and load explicitly only when needed.
Like
class RegisterViewModel: ObservableObject {
#Published var names = [String]()
func loadPerson() {
// probably it also worth checking if person has already loaded
// guard names.isEmpty else { return }
FirebaseManager.fetchNames(success:{(person) in
guard let name = person.name else {return}
DispatchQueue.main.async {
self.names = [name]
}
}){(error) in
print("Error: \(error)")
}
}
struct RegisterView: View {
// in SwiftUI 1.0 it is better to inject view model from outside
// to avoid possible recreation of vm just on parent view refresh
#ObservedObject var vm: RegisterViewModel
// #StateObject var vm = RegisterViewModel() // << only SwiftUI 2.0
var body:some View{
Some_Sub_View()
.onAppear {
self.vm.loadPerson()
}
}
}
Make the names property #State variable.
struct RegisterView: View {
#State var names = [String]()
private func LoadPerson(){
FirebaseManager.fetchNames(success: { person in
guard let name = person.name else { return }
DispatchQueue.main.async {
self.names = [name]
}
}){(error) in
print("Error: \(error)")
}
}
//...
}

Swift - JSONDecoder - passing class Type as parameter to decode model using generic method

Here, we have a scenario where I am facing issue for parsing the model class using "JSONDecoder".
Let me share you what I have done in this example and where I am facing issue:
There's a model protocol derived through the Codable.
A struct model and A class GenericExample
GenericExample class have following things:
shared instance
typeContainer which is the type of Protocol
There's two more methods staticClassParsing and dynamicClassParsing with one parameter dynamicType
In the last, One generic method which return the parsed data model.
Calling method for parsing the model:
GenericExample.shared.dynamicClassParsing(Numbers.self,data: "[\r\n{\"one\": \"1\"},\r\n{\"two\":\"2\"}\r\n]")
Compile Time Error:
Generic parameter 'T' could not be inferred
Occurred here
returnModelType: typeContainer.self
**For more details, please go through following code: **
protocol BaseMapModel : Codable { }
struct Numbers: BaseMapModel {
var one: String?
var two: String?
}
class GenericExample: NSObject {
static let shared = GenericExample()
var typeContainer : BaseMapModel.Type?
private override init() {
super.init()
}
}
extension GenericExample {
// Static Class Parsing passed through the conversion
func staticClassParsing() {
let dataJson = "[\r\n{\"one\": \"1\"},\r\n{\"two\":\"2\"}\r\n]"
convertTypeContainer(data: Data(dataJson.utf8), returnModelType: Numbers.self) { (mappedResult) in
print(mappedResult?.one ?? "")
print(mappedResult?.two ?? "")
}
}
// Dynamic Class Parsing can't passed through the conversion
// Error:- Generic parameter 'T' could not be inferred
// Error Parameter:- in "returnModelType: typeContainer.self"
func dynamicClassParsing(_ dynamicType: BaseMapModel.Type, data:String) {
typeContainer = dynamicType.self
convertTypeContainer(data: Data(data.utf8), returnModelType: typeContainer.self) { (mappedResult) in
print(mappedResult?.one ?? "")
print(mappedResult?.two ?? "")
}
}
}
extension GenericExample {
private func convertTypeContainer<T : BaseMapModel>(data:Data, returnModelType: T.Type, completion: ((_ result:T?)->Void)) {
guard let responseModel = try? JSONDecoder().decode(returnModelType.self, from: data) else {
completion(nil)
return
}
completion(responseModel)
}
}
typeContainer must be a concrete type, it cannot be a protocol. And the completion handler is pointless as JSONDecoder works synchronously.
To decode JSON with generics you have to use something like this, it's highly recommended to handle also the Decoding error
struct Numbers: Decodable {
var one: String?
var two: String?
}
class GenericExample: NSObject {
static let shared = GenericExample()
}
extension GenericExample {
func dynamicClassParsing<T : Decodable>(_ dynamicType: T.Type, data: String) -> Result<T,Error> {
return Result { try JSONDecoder().decode(T.self, from: Data(data.utf8)) }
}
}
let dataJson = """
[{"one": "1"},{"two":"2"}]
"""
let result = GenericExample.shared.dynamicClassParsing([Numbers].self, data: dataJson)
switch result {
case .success(let numbers): print(numbers)
case .failure(let error): print(error)
}
First of all, Thanks to All for your support. And Yes, I would like to post answer to my question.
class BaseMapModel : Codable { }
class Numbers: BaseMapModel {
var one: String?
var two: String?
enum CodingKeys: String, CodingKey {
case one = "one"
case two = "two"
}
// Decoding
required init(from decoder: Decoder) throws {
let response = try decoder.container(keyedBy: CodingKeys.self)
one = try? response.decode(String.self, forKey: .one)
two = try? response.decode(String.self, forKey: .two)
let superDecoder = try response.superDecoder()
try super.init(from: superDecoder)
}
}
class GenericExample: NSObject {
static let shared = GenericExample()
var defaultTypeContainer : Numbers.Type!
var typeContainer : BaseMapModel.Type?
private override init() {
super.init()
}
}
extension GenericExample {
// Static Class Parsing passed through the conversion
func staticClassParsing() {
let dataJson = "[\r\n{\"one\": \"1\"},\r\n{\"two\":\"2\"}\r\n]"
convertTypeContainer(data: Data(dataJson.utf8), returnModelType: Numbers.self) { (mappedResult) in
print(mappedResult?.one ?? "")
print(mappedResult?.two ?? "")
}
}
// Dynamic Class Parsing passed through the conversion
func dynamicClassParsing(_ dynamicType: BaseMapModel.Type, data:String) {
typeContainer = dynamicType.self
convertTypeContainer(data: Data(data.utf8), returnModelType: (typeContainer ?? defaultTypeContainer).self) { (mappedResult) in
print((mappedResult as? Numbers)?.one ?? "")
print((mappedResult as? Numbers)?.two ?? "")
}
}
}
extension GenericExample {
private func convertTypeContainer<T : BaseMapModel>(data:Data, returnModelType: T.Type, completion: ((_ result:T?)->Void)) {
guard let responseModel = try? JSONDecoder().decode(returnModelType.self, from: data) else {
completion(nil)
return
}
completion(responseModel)
}
}

How to make my ReferenceWritableKeyPath class generic

I am using ReferenceWritableKeyPath to modify properties in a model class. This works fine, however, I now want to make my code a bit more generic so that I can modify properties from different classes.
Here is my current code:
final class MyListener {
private let reference: String
private var listener: DatabaseHandle?
private let backendClient: BackendClient
private weak var model: MyModel?
func listenToAndUpdate<T>(_ property: ReferenceWritableKeyPath<MyModel, T?>) {
guard listener == nil else {
return
}
listener = backendClient.listenToRtdbProperty(reference) { [weak self] (result: Result<T, RequestError>) in
switch result {
case .success(let value):
self?.model?[keyPath: property] = value
case .failure:
self?.model?[keyPath: property] = nil
}
}
}
}
I essentially need to have something generic in the model property, but also in the Root of the ReferenceWritableKeyPath. How can I do this? I tried using generics on the class but it wouldn't allow me to have a weak reference to a generic property.
You were on the right track by making the MyListener class generic, but you also need to put a type constraint on the generic type to ensure that it can only be a class, which can have a weak reference. All classes conform to AnyObject, so it is a sufficient type constraint for your case.
final class MyListener<Model: AnyObject> {
private let reference: String
private var listener: DatabaseHandle?
private let backendClient: BackendClient
private weak var model: Model?
func listenToAndUpdate<T>(_ property: ReferenceWritableKeyPath<Model, T?>) {
guard listener == nil else {
return
}
listener = backendClient.listenToRtdbProperty(reference) { [weak self] (result: Result<T, RequestError>) in
switch result {
case .success(let value):
self?.model?[keyPath: property] = value
case .failure:
self?.model?[keyPath: property] = nil
}
}
}
}

Resources