Using zip operator in RxSwift after creating an array of observables - ios

I have a newBid object, that contains some data and images array. I want to upload all images and data to the server and zip those upload observables. If I create separate drivers for data, image1 and image2, I succeed.
But what I want to really do is to not hardcode images since array may contain from 0 to 10 images. I want to create observables array programmatically and zip them.
let dataSaved = saveTaps.withLatestFrom(newBid)
.flatMapLatest { bid in
return CustomerManager.shared.bidCreate(bid: bid)
.trackActivity(activityIndicator)
.asDriver(onErrorJustReturn: false)
}
let photoSaved0 = saveTaps.withLatestFrom(newBid)
.flatMapLatest { bid in
return CustomerManager.shared.bidUploadFile(image: bid.images[0])
.trackActivity(activityIndicator)
.asDriver(onErrorJustReturn: false)
}
let photoSaved1 = saveTaps.withLatestFrom(newBid)
.flatMapLatest { bid in
return CustomerManager.shared.bidUploadFile(image: bid.images[1])
.trackActivity(activityIndicator)
.asDriver(onErrorJustReturn: false)
}
saveCompleted = Driver.zip(dataSaved, photoSaved0, photoSaved1){ return $0 && $1 && $2 }
/*
// 0. Getting array of images from newBid
let images = newBid.map { bid in
return bid.images
}
// 1. Creating array of upload drivers from array of images
let imageUploads = images.map { (images: [UIImage]) -> [Driver<Bool>] in
var temp = [Driver<Bool>]()
return temp
}
// 2. Zipping array of upload drivers to photoSaved driver
photoSaved = Driver
.zip(imageUploads) // wait for all image requests to finish
.subscribe(onNext: { results in
// here you have every single image in the 'images' array
results.forEach { print($0) }
})
.disposed(by: disposeBag)*/
In the commented section if I try to zip imageUploads, I get error:
Argument type 'SharedSequence<DriverSharingStrategy, [SharedSequence<DriverSharingStrategy, Bool>]>' does not conform to expected type 'Collection'

How about something like this?
let saves = saveTaps.withLatestFrom(newBid)
.flatMapLatest { (bid: Bid) -> Observable<[Bool]> in
let dataSaved = CustomerManager.shared.bidCreate(bid: bid)
.catchErrorJustReturn(false)
let photosSaved = bid.images.map {
CustomerManager.shared.bidUploadFile(image: $0, bidID: bid.id)
.catchErrorJustReturn(false)
}
return Observable.zip([dataSaved] + photosSaved)
.trackActivity(activityIndicator)
}
.asDriver(onErrorJustReturn: []) // remove this line if you want an Observable<[Bool]>.

Final solution
let bidID: Driver<Int> = saveTaps.withLatestFrom(newBid)
.flatMapLatest { bid in
return CustomerManager.shared.bidCreate(bid: bid)
.trackActivity(activityIndicator)
.asDriver(onErrorJustReturn: 0)
}
saveCompleted = Driver.combineLatest(bidID, newBid) { bidID, newBid in
newBid.uploadedImages.map {
CustomerManager.shared.bidUploadFile(image: $0, bidID: bidID).asDriver(onErrorJustReturn: false)
}
}.flatMap { imageUploads in
return Driver.zip(imageUploads).trackActivity(activityIndicator).asDriver(onErrorJustReturn: [])
}.map{ (results:[Bool]) -> Bool in
return !results.contains(false)
}
It is a combined version of this which is equivalent:
let imageUploads: Driver<[Driver<Bool>]> = Driver.combineLatest(bidID, newBid) { bidID, newBid in
newBid.uploadedImages.map {
CustomerManager.shared.bidUploadFile(image: $0, bidID: bidID).asDriver(onErrorJustReturn: false)
}
}
let photosSaved: Driver<[Bool]> = imageUploads.flatMap { imageUploads in
return Driver.zip(imageUploads).trackActivity(activityIndicator).asDriver(onErrorJustReturn: [])
}
saveCompleted = photosSaved.map{ (results:[Bool]) -> Bool in
return !results.contains(false)
}

Related

Can't pass variable value from firebase firestore to another class SWIFT

So I have this function in class Functions :
struct Prices {
var standardPrice: Int!
}
// FUNC PRICING
class Functions {
private var PricingRef: CollectionReference!
var price = Prices()
func getPrice() -> Prices {
PricingRef = Firestore.firestore().collection("ProductXYZ")
PricingRef.getDocuments { (snapshot, error) in
if let err = error {
debugPrint("Error fetching data \(err)")
}
else {
guard let snap = snapshot else { return }
for document in snap.documents {
let data = document.data()
let std = data["standard"] as! String
self.price.standardPrice = Int(std)!
print(self.price.standardPrice!) // This print the intended result
}
}
}
return price
}
}
Then I want to pass the standardPrice value to this class, called PriceList :
class PriceList: UITableViewController {
var price = Prices()
var newStandardPrice = 0
func Price() {
price = Functions().getPrice()
newStandardPrice = price.standardPrice // always error with value nil
}
I always have that error where newStandardPrice is nil.
but the print(self.price.standardPrice!) shows number of result I want.
So as far as I know, the problem here is because it takes time for the firebase firestore to get the data from database.
How do I get the value of standardPrice after its assigned with the new price from firebase database?
Any help will be appreciated
Thankyou
you need to use completion handler because its async function
func getPrice(completion:#escaping (Prices?,Error?)-> Void) {
PricingRef = Firestore.firestore().collection("ProductXYZ")
PricingRef.getDocuments { (snapshot, error) in
if let err = error {
debugPrint("Error fetching data \(err)")
completion(nil,err)
}
else {
guard let snap = snapshot else { return }
for document in snap.documents {
let data = document.data()
let std = data["standard"] as! String
self.price.standardPrice = Int(std)!
print(self.price.standardPrice!) // This print the intended result
completion(self.price.standardPrice,nil)
}
}
}
}
How to use
Functions().getPrice { (price, error) in
if let err = error {
// do something if you get error
} else if let getPrice = price {
// use price
self.price = getPriice
}

Documents fetched are replacing the previous loaded documents in tableview

I am using the below code to fetch the data from the firestore database in swift iOS. But when I scroll the new data loaded is replacing the previously loaded data in the tableview. I am trying to fix this issue but as of now no good.
The outcome required is that adding new documents to the previously list of documents in the tableview
Below is the code I am implementing. If any more information is required please let me know
CODE
fileprivate func observeQuery() {
fetchMoreIngredients = true
//guard let query = query else { return }
var query1 = query
stopObserving()
if posts.isEmpty{
query1 = Firestore.firestore().collection("posts").order(by: "timestamp", descending: true).limit(to: 5)
}
else {
query1 = Firestore.firestore().collection("posts").order(by: "timestamp", descending: true).start(afterDocument: lastDocumentSnapshot).limit(to: 2)
// query = db.collection("rides").order(by: "price").start(afterDocument: lastDocumentSnapshot).limit(to: 4)
print("Next 4 rides loaded")
print("hello")
}
// Display data from Firestore, part one
listener = query1!.addSnapshotListener { [unowned self] (snapshot, error) in
guard let snapshot = snapshot else {
print("Error fetching snapshot results: \(error!)")
return
}
let models = snapshot.documents.map { (document) -> Post in
if let model = Post(dictionary: document.data()) {
return model
} else {
// Don't use fatalError here in a real app.
fatalError("Unable to initialize type \(Post.self) with dictionary \(document.data())")
}
}
self.posts = models
self.documents = snapshot.documents
if self.documents.count > 0 {
self.tableView.backgroundView = nil
} else {
self.tableView.backgroundView = self.backgroundView
}
self.tableView.reloadData()
self.fetchMoreIngredients = false
self.lastDocumentSnapshot = snapshot.documents.last
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let off = scrollView.contentOffset.y
let off1 = scrollView.contentSize.height
if off > off1 - scrollView.frame.height * leadingScreensForBatching{
if !fetchMoreIngredients && !reachEnd{
print(fetchMoreIngredients)
// beginBatchFetch()
// query = baseQuery()
observeQuery()
}
}
}
Instead of calling snapshot.documents, call snapshot.documentChanges. This returns a list of document changes (either .added, .modified, or .removed, and allows you to add, remove, or modify them in your local array as needed... Not tested code just an idea what you ca do ...
snapshot.documentChanges.forEach() { diff in
switch diff.type {
case .added:
if let model = Post(dictionary: diff.document.data()){
self.posts.append(model)
}else {
// Don't use fatalError here in a real app.
fatalError("Unable to initialize type \(Post.self) with dictionary \(document.data())")
}
case .removed:
// add remove case
case .modified:
// add modify case
}
}

altering a variable outside a closure

I am currently encountering a problem. I have a function with an array which has items needing appending to. The items are appended in a closure inside the function and I can see the items in the array only inside the closure. Since the function has a return I need the appended items to be viewed by the function as a whole and not just the array. What can I do to solve this?
var trueOrFalse: Bool = false
var tempArray:[String] = []
let reference_message = reference(.Append).whereField("delay", isEqualTo: 0)
reference_message.getDocuments { (snapshot, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let snapshot = snapshot else { return }
let documents = snapshot.documents
if documents != nil {
for document in documents {
let messageID = document[kMESSAGEID] as? String
tempArray.append(messageID!)
//print(trueOrFalse)
}
}
if trueOrFalse {
if opened && trueOrFalse {
print("Successful Walloping")
}
} else if !trueOrFalse {
if !opened || !trueOrFalse {
decryptedText = placeholderText
}
}
return JSQMessage(senderId: userId, senderDisplayName: name, date: date, text: decryptedText)

How do you extract images from PDF using Swift?

I understand PDFKit allows extracting text+formatting as NSAttributedString, but I can't find any info on extracting each individual figures from any PDF document using Swift.
Any help would be greatly appreciated, thanks!
edit: https://stackoverflow.com/a/40788449/2303865 explains how to convert the whole page into image, however I need to parse all images already part of the a series of PDF documents, without knowing where they are located, so that solution is not appropriate to my question.
Here is a Swift function that extracts images, more specifically all Objects with Subtype "Image" from pdf pages:
import PDFKit
func extractImages(from pdf: PDFDocument, extractor: #escaping (ImageInfo)->Void) throws {
for pageNumber in 0..<pdf.pageCount {
guard let page = pdf.page(at: pageNumber) else {
throw PDFReadError.couldNotOpenPageNumber(pageNumber)
}
try extractImages(from: page, extractor: extractor)
}
}
func extractImages(from page: PDFPage, extractor: #escaping (ImageInfo)->Void) throws {
let pageNumber = page.label ?? "unknown page"
guard let page = page.pageRef else {
throw PDFReadError.couldNotOpenPage(pageNumber)
}
guard let dictionary = page.dictionary else {
throw PDFReadError.couldNotOpenDictionaryOfPage(pageNumber)
}
guard let resources = dictionary[CGPDFDictionaryGetDictionary, "Resources"] else {
throw PDFReadError.couldNotReadResources(pageNumber)
}
if let xObject = resources[CGPDFDictionaryGetDictionary, "XObject"] {
print("reading resources of page", pageNumber)
func extractImage(key: UnsafePointer<Int8>, object: CGPDFObjectRef, info: UnsafeMutableRawPointer?) -> Bool {
guard let stream: CGPDFStreamRef = object[CGPDFObjectGetValue, .stream] else { return true }
guard let dictionary = CGPDFStreamGetDictionary(stream) else {return true}
guard dictionary.getName("Subtype", CGPDFDictionaryGetName) == "Image" else {return true}
let colorSpaces = dictionary.getNameArray(for: "ColorSpace") ?? []
let filter = dictionary.getNameArray(for: "Filter") ?? []
var format = CGPDFDataFormat.raw
guard let data = CGPDFStreamCopyData(stream, &format) as Data? else { return false }
extractor(
ImageInfo(
name: String(cString: key),
colorSpaces: colorSpaces,
filter: filter,
format: format,
data: data
)
)
return true
}
CGPDFDictionaryApplyBlock(xObject, extractImage, nil)
}
}
struct ImageInfo: CustomDebugStringConvertible {
let name: String
let colorSpaces: [String]
let filter: [String]
let format: CGPDFDataFormat
let data: Data
var debugDescription: String {
"""
Image "\(name)"
- color spaces: \(colorSpaces)
- format: \(format == .JPEG2000 ? "JPEG2000" : format == .jpegEncoded ? "jpeg" : "raw")
- filters: \(filter)
- size: \(ByteCountFormatter.string(fromByteCount: Int64(data.count), countStyle: .binary))
"""
}
}
extension CGPDFObjectRef {
func getName<K>(_ key: K, _ getter: (OpaquePointer, K, UnsafeMutablePointer<UnsafePointer<Int8>?>)->Bool) -> String? {
guard let pointer = self[getter, key] else { return nil }
return String(cString: pointer)
}
func getName<K>(_ key: K, _ getter: (OpaquePointer, K, UnsafeMutableRawPointer?)->Bool) -> String? {
guard let pointer: UnsafePointer<UInt8> = self[getter, key] else { return nil }
return String(cString: pointer)
}
subscript<R, K>(_ getter: (OpaquePointer, K, UnsafeMutablePointer<R?>)->Bool, _ key: K) -> R? {
var result: R!
guard getter(self, key, &result) else { return nil }
return result
}
subscript<R, K>(_ getter: (OpaquePointer, K, UnsafeMutableRawPointer?)->Bool, _ key: K) -> R? {
var result: R!
guard getter(self, key, &result) else { return nil }
return result
}
func getNameArray(for key: String) -> [String]? {
var object: CGPDFObjectRef!
guard CGPDFDictionaryGetObject(self, key, &object) else { return nil }
if let name = object.getName(.name, CGPDFObjectGetValue) {
return [name]
} else {
guard let array: CGPDFArrayRef = object[CGPDFObjectGetValue, .array] else {return nil}
var names = [String]()
for index in 0..<CGPDFArrayGetCount(array) {
guard let name = array.getName(index, CGPDFArrayGetName) else { continue }
names.append(name)
}
return names
}
}
}
enum PDFReadError: Error {
case couldNotOpenPageNumber(Int)
case couldNotOpenPage(String)
case couldNotOpenDictionaryOfPage(String)
case couldNotReadResources(String)
case cannotReadXObjectStream(xObject: String, page: String)
}
You should know that images in PDFs can be represented in different ways. They can be embedded as self contained JPGs or they can be embedded as raw pixel data (lossless compressed or not) with meta information about the compression, color space, width, height, and so forth.
So if you want to export embedded JPGs: this code works just fine. But if you also want to visualise the raw images you will need even more parsing code. To get started you can look at the PDF 2.0 spec (or an older free version of the spec), and this gist which interprets JPGs in any color profile and raw images with any of the following color profiles:
DeviceGray
DeviceRGB
DeviceCMYK
Indexed
ICCBased

Accessing Data Outside of Closure

I have an array in my class that I'm trying to fill within a closure. However, when I try to access/print the array contents, it seems to be empty outside of the closure. How do I store the data for use outside of the closure?
for index in 0..<6 {
let picNumber = index + 1
if let pica = currentuser.objectForKey("pic\(picNumber)") as? PFFile {
pica.getDataInBackgroundWithBlock({ (data:NSData!, error:NSError!) -> Void in
if error == nil {
self.pic1 = UIImage(data: data)
var imageS = scaleImage(self.pic1!, and: 200)
self.imagesForSection0.append(imageS)
}
})
println(self.imagesForSection0)
}
}
It is not empty outside the Closure
The method getDataInBackgroundWithBlock is async,it means that it will return first.So,you see nothing in print function.
Document
Asynchronously gets the data from cache if available or fetches its contents from the network.
Edit
for index in 0..<6 {
let picNumber = index + 1
if let pica = currentuser.objectForKey("pic\(picNumber)") as? PFFile {
pica.getDataInBackgroundWithBlock({ (data:NSData!, error:NSError!) -> Void in
if error == nil {
self.pic1 = UIImage(data: data)
var imageS = scaleImage(self.pic1!, and: 200)
self.imagesForSection0.append(imageS)
}
println(self.imagesForSection0)
//Then call reload data
})
}
}

Resources