ION-Client for iOS and OS X clients
- iOS 13.0+
- Xcode 13.2.1+
- Swift 5.5+
- In Xcode: Select
File->Add Package-> Search forhttps://github.com/anfema/ion-client-ios.git - Select
IONClientdependency and specify Dependency Rule import IONClientin source file to access the kit.
- Setup
- Getting collections
- Getting pages
- Getting outlets
- Content types
- Error handling
- Resetting caches
To use the ION client you'll need to supply some basic information:
- Base URL to the API server
- Locale to use
Example:
ION.config.serverURL = NSURL(string: "http://127.0.0.1:8000/client/v1/")
ION.config.locale = "en_US"To be sure to produce no conflict with later ION calls put this in your AppDelegate.
Server URL and locale may be changed at any time afterwards, existing data is not deleted and stays cached.
For logging in call the login function of the ION class or supply a sessionToken in the configuration when you decide to not use the login functionality of the ION server.
Example:
ION.login("testuser@example.com", password:"password") { success in
print("login " + (success ? "suceeeded" : "failed"))
}All calls run through the ION class and it's class functions. Most calls have synchronous and asynchronous variants, the sync variants should only be used when you are sure the object is in the cache.
Async variant:
ION.collection("collection001") { result in
guard case .Success(let collection) = result else {
print("Error: ", result.error ?? .UnknownError)
}
print("loaded collection \(collection.identifier)")
}Sync variant:
let collection = ION.collection("collection001")The sync variant fetches its values in the background async, so not all values will be available directly, but all appended calls to such an unfinished collection will be queued until it is available and called then.
DO NOT use for getting to a collection only, use the async variant for that, USE to queue calls!
Fetching pages can be done in multiple ways.
ION.collection("collection001").page("page001") { result in
guard case .Success(let page) = result else {
print("Error: ", result.error ?? .UnknownError)
}
print("page \(page.identifier) loaded")
}Variant 1:
ION.collection("collection001") { result in
guard case .Success(let collection) = result else {
print("Error: ", result.error ?? .UnknownError)
}
collection.page("page001") { result in
guard case .Success(let page) = result else {
print("Error: ", result.error ?? .UnknownError)
}
print("page \(page.identifier) loaded")
}
collection.page("page002") { result in
guard case .Success(let page) = result else {
print("Error: ", result.error ?? .UnknownError)
}
print("page \(page.identifier) loaded")
}
}Variant 2:
let collection = ION.collection("collection001")
collection.page("page001") { result in
guard case .Success(let page) = result else {
print("Error: ", result.error ?? .UnknownError)
}
print("page \(page.identifier) loaded")
}
collection.page("page002") { page in
guard case .Success(let page) = result else {
print("Error: ", result.error ?? .UnknownError)
}
print("page \(page.identifier) loaded")
}Be aware that no collection updates will be reflected if you cache a collection object this way.
ION.collection("collection001").page("page001").child("subpage001") { result in
guard case .Success(let page) = result else {
print("Error: ", result.error ?? .UnknownError)
}
print("sub-page \(page.identifier) loaded")
}ION.collection("collection001").metadata("page001") { result in
guard case .Success(let metadata) = result else {
print("Error: ", result.error ?? .UnknownError)
}
print("page title \(metadata.title)")
metadata.image { thumbnail in
print("thumbnail size: \(image.size.width) x \(image.size.height)")
}
}This works analogous to pages but has some convenience functionality.
In a page(identifier: String, callback:(IONPage -> Void)) callback block you may use the sync variants as the page you just fetched contains the content.
The result of an outlet(*) call is always an IONContent object. To get the values of the content object you'll need a switch:
switch(content) {
case let t as IONTextContent:
print(t.plainText())
...
default:
break
}or at least a if case let:
if case let t as IONTextContent = content {
print(t.plainText())
}ION.collection("collection001").page("page001").outlet("title") { result in
guard case .Success(let content) = result else {
print("Error: ", result.error ?? .UnknownError)
}
print("outlet \(content.outlet) loaded")
}Variant 1:
ION.collection("collection001").page("page001") { result in
guard case .Success(let page) = result else {
print("Error: ", result.error ?? .UnknownError)
}
page.outlet("title") { result in
guard case .Success(let outlet) = result else {
print("Error: ", result.error ?? .UnknownError)
}
print("outlet \(content.outlet) loaded")
}
page.outlet("text") { result in
guard case .Success(let content) = result else {
print("Error: ", result.error ?? .UnknownError)
}
print("outlet \(content.outlet) loaded")
}
}Variant 2:
ION.collection("collection001").page("page001").outlet("title") { result in
guard case .Success(let content) = result else {
print("Error: ", result.error ?? .UnknownError)
}
print("outlet \(content.outlet) loaded")
}.outlet("text") { result in
guard case .Success(let content) = result else {
print("Error: ", result.error ?? .UnknownError)
}
print("outlet \(content.outlet) loaded")
}ION.collection("collection001").page("page001") { result in
guard case .Success(let page) = result else {
print("Error: ", result.error ?? .UnknownError)
}
for content in page.content {
print("outlet \(content.outlet) loaded")
}
}The following content types are defined:
- Color
- Container
- DateTime
- File
- Flag
- Image
- Media
- Option
- Text
All content types have the base class of IONContentBase. All content types extend the IONPage object with some convenience functions to make getting their values easier, following is a list with all available functions.
Content object:
color() -> XXColor: ReturnsUIColororNSColorinstances of the object
Page extension:
cachedColor(name: String) -> XXColor: ReturnsUIColororNSColorinstances of a color outlet if already cachedcolor(name: String, callback: (XXColor -> Void)) -> IONPage: Calls callback withUIColororNSColorinstance, returnsselffor further chaining
Content object may be subscripted with an Int to get a child.
Page extension:
children(name: String) -> [IONContent]?: Returns a list of children if the container was cached elsenilchildren(name: String, callback: ([IONContent] -> Void)) -> IONPage: Calls callback with list of children (if there are some), returnsselffor further chaining
Content object has a date property that contains the parsed NSDate
Page extension:
date(name: String) -> NSDate?: Returns a date if the value was cached alreadydate(name: String, callback: (Result<NSDate, IONError> -> Void)) -> IONPage: Calls callback withNSDateobject, returnsselffor further chaining
Content object:
data(callback: (Result<NSData, IONError> -> Void)): Returns memory mappedNSDatafor the content of the file, this initiates the file download if it is not in the cachedataProvider(callback: (Result<CGDataProviderRef, IONError> -> Void))Calls a callback with aCGDataProviderfor the modified image dataoriginalDataProvider(callback: (Result<CGDataProviderRef, IONError> -> Void))Calls a callback with aCGDataProviderfor the original image datacgImage(original: Bool = false, callback: (Result<CGImageRef, IONError> -> Void))Calls a callback with aCGImagefor the image dataimage(callback: (Result<XXImage, IONError> -> Void))Calls a callback with aUIImageorNSImagefor the modified image dataoriginalImage(callback: (Result<XXImage, IONError> -> Void))Calls a callback with aUIImageorNSImagefor the original image data
Page extension:
fileData(name: String, callback:(Result<NSData, IONError> -> Void)) -> IONPage: Calls callback withNSDatainstance for the content of the file, returnsselffor further chaining
Content object has a enabled property.
Page extension:
isSet(name: String) -> Bool?: Returns if the flag is set when the value is cached already, elsenilisSet(name: String, callback: (Result<Bool, IONError> -> Void)) -> IONPage: Calls callback with flag status, returnsselffor further chaining
Content object:
dataProvider(callback: (Result<CGDataProviderRef, IONError> -> Void))Calls a callback with aCGDataProviderfor the modified image dataoriginalDataProvider(callback: (Result<CGDataProviderRef, IONError> -> Void))Calls a callback with aCGDataProviderfor the original image datacgImage(original: Bool = false, callback: (Result<CGImageRef, IONError> -> Void))Calls a callback with aCGImagefor the image dataimage(callback: (Result<XXImage, IONError> -> Void))Calls a callback with aUIImageorNSImagefor the modified image dataoriginalImage(callback: (Result<XXImage, IONError> -> Void))Calls a callback with aUIImageorNSImagefor the original image data
Page extension:
image(name: String, callback: (Result<XXImage, IONError> -> Void)) -> IONPage: Calls callback with modified image (eitherUIImageorNSImage), returnsselffor further chainingoriginalImage(name: String, callback: (Result<XXImage, IONError> -> Void)) -> IONPage: Calls callback with original image (eitherUIImageorNSImage), returnsselffor further chaining
Content object:
data(callback: (Result<NSData, IONError> -> Void)): Calls a callback with memory mappedNSDatafor the media file, Warning: this downloads the file!dataProvider(callback: (Result<CGDataProviderRef, IONError> -> Void))Calls a callback with aCGDataProviderfor the modified image dataoriginalDataProvider(callback: (Result<CGDataProviderRef, IONError> -> Void))Calls a callback with aCGDataProviderfor the original image datacgImage(original: Bool = false, callback: (Result<CGImageRef, IONError> -> Void))Calls a callback with aCGImagefor the image dataimage(callback: (Result<XXImage, IONError> -> Void))Calls a callback with aUIImageorNSImagefor the modified image dataoriginalImage(callback: (Result<XXImage, IONError> -> Void))Calls a callback with aUIImageorNSImagefor the original image data- Use
urlproperty to get the URL of the file!
Page extension:
mediaURL(name: String) -> NSURL?: Returns the URL to the media file if value was cached, elsenilmediaURL(name: String, callback: (Result<NSURL, IONError> -> Void)) -> IONPage: Calls callback with the URL to the media file, returnsselffor further chainingmediaData(name: String, callback: (Result<NSData, IONError> -> Void)) -> IONPage: Calls callback with aNSDatainstance for the media file, Warning: this downloads the file! Returnsselffor further chaining
Content object has a value property.
Page extension:
selectedOption(name: String) -> String?: Returns the selected option if cached, elsenilselectedOption(name: String, callback: (Result<String, IONError> -> Void)) -> IONPage: Calls callback with selected option, returnsselffor further chaining
Content object:
htmlText() -> String?: Returns html formatted text, see function docs for more infoattributedString() -> NSAttributedString?: ReturnsNSAttributedStringformatted text, see function docs for more infoplainText() -> String?: Returns plain text string, see function docs for more info
Page extension:
text(name: String) -> String?: Returns the plain text version if cached, elseniltext(name: String, callback: (Result<String, IONError> -> Void)) -> IONPage: Calls callback with plain text version, returnsselffor further chaining
All callbacks return a Result<T, IONError> enum.
There are two cases in that enum:
.Success(_ data: T).Failure(_ error: IONError)
so primarily you have to guard all accesses to the returned content:
guard case .Success(let content) = result else {
print("Error: ", result.error ?? .UnknownError)
}
// use `content`Here we assume the variable result is your input.
By default all caches are kept in sync with the server by calling a collection update all 10 minutes and invalidating stale objects after that call. The cache can only function correctly if you do not cache collection or page objects yourself. So best practice is always taking the ION.collection("x").page("y") { // usage } road. All calls that are processed that way are fully memory cached, so they should be both: fast and current.
All accessed collections and pages will be kept in a memory cache to avoid constantly parsing their JSON representation from disk. Memory cache should be cleared on memory warnings as all objects in that cache may be easily reinstated after a purge with a small runtime penalty (disk access, JSON parsing)
Example:
ION.resetMemCache()Disk cache is kept per server and locale, so to wipe all disk caches you'll have to go through all locales and hosts you use. That cache contains JSON request responses and all downloaded media. Beware if the media files come from another server than you send the api requests to they will be saved in a directory for that hostname, not in the API cache.
Example:
ION.resetDiskCache() // resets the cache for the current configThe first call to any collection after App-start will automatically cause a server-fetch.
You can set the cache timeout by setting ION.config.cacheTimeout, by default it is set to 600 seconds.
To force a collection update set ION.config.lastOnlineUpdate to nil.