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
IONClient
dependency and specify Dependency Rule import IONClient
in 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
: ReturnsUIColor
orNSColor
instances of the object
Page extension:
cachedColor(name: String) -> XXColor
: ReturnsUIColor
orNSColor
instances of a color outlet if already cachedcolor(name: String, callback: (XXColor -> Void)) -> IONPage
: Calls callback withUIColor
orNSColor
instance, returnsself
for 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 elsenil
children(name: String, callback: ([IONContent] -> Void)) -> IONPage
: Calls callback with list of children (if there are some), returnsself
for 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 withNSDate
object, returnsself
for further chaining
Content object:
data(callback: (Result<NSData, IONError> -> Void))
: Returns memory mappedNSData
for 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 aCGDataProvider
for the modified image dataoriginalDataProvider(callback: (Result<CGDataProviderRef, IONError> -> Void))
Calls a callback with aCGDataProvider
for the original image datacgImage(original: Bool = false, callback: (Result<CGImageRef, IONError> -> Void))
Calls a callback with aCGImage
for the image dataimage(callback: (Result<XXImage, IONError> -> Void))
Calls a callback with aUIImage
orNSImage
for the modified image dataoriginalImage(callback: (Result<XXImage, IONError> -> Void))
Calls a callback with aUIImage
orNSImage
for the original image data
Page extension:
fileData(name: String, callback:(Result<NSData, IONError> -> Void)) -> IONPage
: Calls callback withNSData
instance for the content of the file, returnsself
for 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, elsenil
isSet(name: String, callback: (Result<Bool, IONError> -> Void)) -> IONPage
: Calls callback with flag status, returnsself
for further chaining
Content object:
dataProvider(callback: (Result<CGDataProviderRef, IONError> -> Void))
Calls a callback with aCGDataProvider
for the modified image dataoriginalDataProvider(callback: (Result<CGDataProviderRef, IONError> -> Void))
Calls a callback with aCGDataProvider
for the original image datacgImage(original: Bool = false, callback: (Result<CGImageRef, IONError> -> Void))
Calls a callback with aCGImage
for the image dataimage(callback: (Result<XXImage, IONError> -> Void))
Calls a callback with aUIImage
orNSImage
for the modified image dataoriginalImage(callback: (Result<XXImage, IONError> -> Void))
Calls a callback with aUIImage
orNSImage
for the original image data
Page extension:
image(name: String, callback: (Result<XXImage, IONError> -> Void)) -> IONPage
: Calls callback with modified image (eitherUIImage
orNSImage
), returnsself
for further chainingoriginalImage(name: String, callback: (Result<XXImage, IONError> -> Void)) -> IONPage
: Calls callback with original image (eitherUIImage
orNSImage
), returnsself
for further chaining
Content object:
data(callback: (Result<NSData, IONError> -> Void))
: Calls a callback with memory mappedNSData
for the media file, Warning: this downloads the file!dataProvider(callback: (Result<CGDataProviderRef, IONError> -> Void))
Calls a callback with aCGDataProvider
for the modified image dataoriginalDataProvider(callback: (Result<CGDataProviderRef, IONError> -> Void))
Calls a callback with aCGDataProvider
for the original image datacgImage(original: Bool = false, callback: (Result<CGImageRef, IONError> -> Void))
Calls a callback with aCGImage
for the image dataimage(callback: (Result<XXImage, IONError> -> Void))
Calls a callback with aUIImage
orNSImage
for the modified image dataoriginalImage(callback: (Result<XXImage, IONError> -> Void))
Calls a callback with aUIImage
orNSImage
for the original image data- Use
url
property to get the URL of the file!
Page extension:
mediaURL(name: String) -> NSURL?
: Returns the URL to the media file if value was cached, elsenil
mediaURL(name: String, callback: (Result<NSURL, IONError> -> Void)) -> IONPage
: Calls callback with the URL to the media file, returnsself
for further chainingmediaData(name: String, callback: (Result<NSData, IONError> -> Void)) -> IONPage
: Calls callback with aNSData
instance for the media file, Warning: this downloads the file! Returnsself
for further chaining
Content object has a value
property.
Page extension:
selectedOption(name: String) -> String?
: Returns the selected option if cached, elsenil
selectedOption(name: String, callback: (Result<String, IONError> -> Void)) -> IONPage
: Calls callback with selected option, returnsself
for further chaining
Content object:
htmlText() -> String?
: Returns html formatted text, see function docs for more infoattributedString() -> NSAttributedString?
: ReturnsNSAttributedString
formatted 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, elsenil
text(name: String, callback: (Result<String, IONError> -> Void)) -> IONPage
: Calls callback with plain text version, returnsself
for 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 config
The 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
.