Swift Get Next Page from header of NSHTTPURLResponse - swift

I am consuming an API that gives me the next page in the Header inside a field called Link. (For example Github does the same, so it isn't weird.Github Doc)
The service that I am consuming retrieve me the pagination data in the following way:
As we can see in the "Link" gives me the next page,
With $0.response?.allHeaderFields["Link"]: I get </api/games?page=1&size=20>; rel="next",</api/games?page=25&size=20>; rel="last",</api/games?page=0&size=20>; rel="first".
I have found the following code to read the page, but it is very dirty... And I would like if anyone has dealt with the same problem or if there is a standard way of face with it. (I have also searched if alamofire supports any kind of feature for this but I haven't found it)
// MARK: - Pagination
private func getNextPageFromHeaders(response: NSHTTPURLResponse?) -> String? {
if let linkHeader = response?.allHeaderFields["Link"] as? String {
/* looks like:
<https://api.github.com/user/20267/gists?page=2>; rel="next", <https://api.github.com/user/20267/gists?page=6>; rel="last"
*/
// so split on "," the on ";"
let components = linkHeader.characters.split {$0 == ","}.map { String($0) }
// now we have 2 lines like '<https://api.github.com/user/20267/gists?page=2>; rel="next"'
// So let's get the URL out of there:
for item in components {
// see if it's "next"
let rangeOfNext = item.rangeOfString("rel=\"next\"", options: [])
if rangeOfNext != nil {
let rangeOfPaddedURL = item.rangeOfString("<(.*)>;", options: .RegularExpressionSearch)
if let range = rangeOfPaddedURL {
let nextURL = item.substringWithRange(range)
// strip off the < and >;
let startIndex = nextURL.startIndex.advancedBy(1) //advance as much as you like
let endIndex = nextURL.endIndex.advancedBy(-2)
let urlRange = startIndex..<endIndex
return nextURL.substringWithRange(urlRange)
}
}
}
}
return nil
}

I think that the forEach() could have a better solution, but here is what I got:
let linkHeader = "</api/games?page=1&size=20>; rel=\"next\",</api/games?page=25&size=20>; rel=\"last\",</api/games?page=0&size=20>; rel=\"first\""
let links = linkHeader.components(separatedBy: ",")
var dictionary: [String: String] = [:]
links.forEach({
let components = $0.components(separatedBy:"; ")
let cleanPath = components[0].trimmingCharacters(in: CharacterSet(charactersIn: "<>"))
dictionary[components[1]] = cleanPath
})
if let nextPagePath = dictionary["rel=\"next\""] {
print("nextPagePath: \(nextPagePath)")
}
//Bonus
if let lastPagePath = dictionary["rel=\"last\""] {
print("lastPagePath: \(lastPagePath)")
}
if let firstPagePath = dictionary["rel=\"first\""] {
print("firstPagePath: \(firstPagePath)")
}
Console output:
$> nextPagePath: /api/games?page=1&size=20
$> lastPagePath: /api/games?page=25&size=20
$> firstPagePath: /api/games?page=0&size=20
I used components(separatedBy:) instead of split() to avoid the String() conversion at the end.
I created a Dictionary for the values to hold and removed the < and > with a trim.

Related

Using Swift .split to format an array

I am reading in a text file of translation pairs of this format:
boy:garçon
garçon:boy
Into an array using the following code:
var vocab:[String:String] = [:]
let path = Bundle.main.path(forResource: "words_alpha", ofType: "txt")!
let text = try! String(contentsOfFile: path, encoding: String.Encoding.utf8)
let vocab = text.components(separatedBy: CharacterSet.newlines)
The imported array looks like this:
["boy:garçon", "garçon:boy"]
Whereas I would like the array to be formatted like this:
["boy":"garçon", "garçon":"boy"]
What is the best way to achieve the desired array format shown above using a Swift string transformation?
Have been trying to use .split, but with not much success.
Let's be clear:
["boy":"garçon", "garçon":"boy"]
That's a Dictionary, not an Array.
There a multiples ways to do that, here's two possible codes:
var manual: [String: String] = [:]
array.forEach { aString in
let components = aString.components(separatedBy: ":")
guard components.count == 2 else { return }
manual[components[0]] = components[1]
}
print(manual)
or
let reduced = array.reduce(into: [String: String]()) { result, current in
let components = current.components(separatedBy: ":")
guard components.count == 2 else { return }
result[components[0]] = components[1]
}
print(reduced)
Output (for both):
$> ["garçon": "boy", "boy": "garçon"]
As said, it's a Dictionary, so there is no guarantee that the print be:
["garçon": "boy", "boy": "garçon"] or ["boy":"garçon", "garçon":"boy"], it's key-value access, not index-value access.

How do I apply my custom attribute in an AttributedString in SwiftUI

I have my custom attribute here, just for testing, the goal is to swap letters just to see how I can affect the string
enum SwapAttribute : AttributedStringKey {
typealias Value = String
static let name = "swap"
}
extension AttributeScopes {
struct MyTextStyleAttributes: AttributeScope {
let swap: SwapAttribute
}
}
extension AttributeDynamicLookup {
subscript<T: AttributedStringKey>(dynamicMember keyPath: KeyPath<AttributeScopes.MyTextStyleAttributes, T>) -> T {
return self[T.self]
}
}
func attributesTest() {
var str = AttributedString("It's a secret")
let secret = str.range(of: "secret")!
str[secret].swap = "*"
// expect 'It's a ******'
print(str)
}
I can add my attribute, and I can see it in the .runs variable, but all of the documentation and tutorials gloss over this. How do I execute code or apply changes to the string.
(or perhaps this is intended for other things)
Custom Implementations
Custom attributes only serve as data storage, they are not displayed or processed by SwiftUI or UIKit. Additional custom behavior would have to be implemented manually.
Workaround
For behavior as described in the question, stick to extensions.
var str = AttributedString("It's a ******")
let secret = str.range(of: "******")!
str[secret].swap = "secret"
That would display "It's a ******", while still holding your "secret" data in the attributes.
To improve on that, you could create an extension:
extension AttributedString {
func swapping(_ value: String, with new: Character) -> AttributedString {
let mutableAttributedString = NSMutableAttributedString(self)
let range = mutableAttributedString.mutableString.range(of: value)
let newString = String(repeating: new, count: range.length)
// Use replaceCharacters(in:with:) to keep all original attributes
mutableAttributedString.replaceCharacters(in: range, with: newString)
// Set the "secret" data
var str = AttributedString(mutableAttributedString)
str[str.range(of: newString)!].swap = value
return str
}
}
var str = AttributedString("It's a secret")
.swapping("secret", with: "*")
print(str)
The print output:
It's a {
}
****** {
swap = secret
}

How to get an array from Firestore document on Swift 5?

Please help me understand the logic reading data from the Firestore document, if one of the values is an array. I tried other answers here and sources but never came to a simple working way and understand clearly. Firestore document structure — example. And Swift class targets for saving (conditional):
struct MyStruct {
var name: String
var pages: Int
}
let part1 = [MyStruct]()
let name1 = ""
let pages1 = 0
let part2 = [MyStruct]()
let name2 = ""
let pages2 = 0
func readFirestore() { }
What should the document reader function look like to add data to existing targets in the class? Thanks in advance for any help in improving my understanding!
They helped to deal with familiar, thank you for what they are. As expected, everything is simple. But for the beginner there is nothing more difficult than simplicity 😁
func readFirestore() {
self.db.collection("example").document("book").getDocument { (document, error) in
if error == nil {
if document != nil && document!.exists {
//get all document data
guard let documentData = document!.data() else {return}
//get value-array for key "part1"
let element = documentData["part1"] as? [Any] //print -> Optional([name1, 100])
//get first element in array
guard let nameDB = element?[0] as? String else {return} //print -> name1
guard let pagesDB = element?[1] as? String else {return} //print -> 100
//append in class
part1.append(MyStruct(name: nameDB, pages: pagesDB))
name1 = nameDB
pages1 = pagesDB
}
}
}
}

Ambiguous reference to member 'count' #2

I have these two functions:
func search_DocumentMultimedia(documentId: Int) -> Array<Int>{
var array:[Int]
let downloadUrl = FileManager.default.urls(for:.downloadsDirectory, in: .userDomainMask)[0]
let path = downloadUrl.appendingPathComponent("mwb_" + language + "_" + issue + ".db")
do{
let db = try Connection(path.absoluteString)
let documentMultimediaTable = Table("DocumentMultimedia")
let documentMultimediaIdField = Expression<Int>("DocumentMultimediaId")
let documentIdField = Expression<Int>("DocumentId")
for media in try db.prepare(documentMultimediaTable.select(documentMultimediaIdField, documentIdField).where(documentIdField == documentId)) {
array.append(media[documentMultimediaIdField])
}
} catch{
print(error)
}
return array
}
func search_Multimedia(multimediaID: [Int]) -> Array<mediaData>{
var array:[mediaData]
let downloadUrl = FileManager.default.urls(for:.downloadsDirectory, in: .userDomainMask)[0]
let path = downloadUrl.appendingPathComponent("mwb_" + language + "_" + issue + ".db")
ForEach(0..<multimediaID.count, id: \.self) { index in
do{
let db = try Connection(path.absoluteString)
let documentMultimediaTable = Table("Multimedia")
let MultimediaIdField = Expression<Int>("MultimediaId")
let keySymbol = Expression<String>("KeySymbol")
let track = Expression<Int>("Track")
let issueTagNumber = Expression<Int>("IssueTagNumber")
let mimeType = Expression<String>("MimeType")
let filePath = Expression<String>("FilePath")
for media in try db.prepare(documentMultimediaTable.select(MultimediaIdField, keySymbol, track, issueTagNumber, mimeType, filePath).where(MultimediaIdField == multimediaID[index])) {
let data = mediaData(mediaID: media[MultimediaIdField], mediaType: media[keySymbol], track: media[track], issueTagNumber: media[issueTagNumber], mimeType: media[mimeType], filePath: media[filePath])
array.append(data)
}
} catch{
print(error)
}
}
return array
}
I call them like this:
func getMedia(documentId: Int, nextDocumentId: Int) /*-> Array<videoData>*/ {
let multimediaId:[Int] = JW.search_DocumentMultimedia(documentId: documentId)
let mediaData:[mediaData] = JW.search_Multimedia(multimediaID: multimediaId)
print(mediaData)
}
In search_Multimedia I keep getting an error that says 'Ambiguous reference to member 'count''. I get this error on ForEach statement that uses multimediaID.count. I have tried everything but can't find how to resolve this. Please could you lend a hand? I saw a similar question on here but it seems to be outdated - hence my post.
You are using ForEach where ForEach is not appropriate. The purpose of ForEach is to turn a collection of values into a collection of SwiftUI Views inside the body of some collection view like a List or a VStack.
You don't need to create a collection of Views here. Use a regular Swift for/in statement. Replace this:
ForEach(0..<multimediaID.count, id: \.self) { index in
with this:
for index in 0 ..< multimediaID.count {
I think you have other errors, but this one is particularly important. ForEach (like most SwiftUI types) relies heavily on type inference. Mistakes in a ForEach call can seriously confuse the Swift compiler's type inference implementation. Then the compiler often prints useless or misleading error messages. So if you replace your ForEach with for/in loop, the compiler will probably give you better messages about any other errors you have made in this code.

Use SwiftyJSON to get proper data

This is my JSON data, how can I get src data in 0 in pickArray?
"pickArray" : "{\"0\":{\"src\":\"https:\/\/fb-s-d-a.akamaihd.net\/h-ak-xpl1\/v\/t1.0-9\/p720x720\/18010403_1525007564199498_8009700960533638318_n.png?oh=25dbc9c1522dcfdd1d15cdd3e8c0c7da&oe=59997685&__gda__=1502470695_f212ade003e9b1c4ddc6a3ab6cc9e7e7\",\"width\":720,\"height\":720}}"
If I do it like this:
let dataArray = json["pickArray"]
print("dataArray = ",dataArray)
dataArray = {"0":{"src":"https://fb-s-d-a.akamaihd.net/h-ak-xpl1/v/t1.0-9/p720x720/18010403_1525007564199498_8009700960533638318_n.png?oh=25dbc9c1522dcfdd1d15cdd3e8c0c7da&oe=59997685&__gda__=1502470695_f212ade003e9b1c4ddc6a3ab6cc9e7e7","width":720,"height":720}}
But if I do it like this, show null:
let srcArray = dataArray["0"]
print("srcArray = ",srcArray)
I'm using swift3.0
Its looks like that with key pickArray you are having JSON response in String so get that string and convert it data and get JSON from it and then get src from it.
let stringResponse = json["pickArray"].stringValue
if let data = stringResponse.data(using: .utf8) {
let pickArray = JSON(data: data)
//Now access the pickArray to get the src
var sortedKeys = [String]()
if let allKeys = pickArray.dictionaryObject {
sortedKeys = Array(allKeys.keys).sorted { $0.compare($1, options: .numeric) == .orderedAscending }
}
for key in sortedKeys {
print(pickArray[key]["src"].stringValue)
print(pickArray[key]["width"].intValue)
print(pickArray[key]["height"].intValue)
}
}
let srcArray = dataArray["0"].dictionaryObject!
print("srcArray = \(srcArray)")
Now you can access element of "0" value as like below. Hope this work for you.
let jsonScr = JSON(srcArray)
let srcURL = jsonScr["scr"].stringValue