stream-swift is a Swift client for Stream.
You can sign up for a Stream account at https://getstream.io/get_started.
For Stream, use the following entry in your Podfile:
pod 'GetStream', '~> 1.0'
Then run pod install.
In any file you'd like to use Stream in, don't forget to import the framework with import GetStream.
To integrate using Apple's Swift package manager, add the following as a dependency to your Package.swift:
.package(url: "https://github.com/GetStream/stream-swift.git", .upToNextMajor(from: "1.0.0"))
Make the following entry in your Cartfile:
github "GetStream/stream-swift"
Then run carthage update.
Project is maintained by Alexey Bukhtin.
We continue to welcome pull requests from community members.
Copyright (c) 2016-2018 Stream.io Inc, and individual contributors. All rights reserved.
See the file "LICENSE" for information on the history of this software, terms & conditions for usage, and a DISCLAIMER OF ALL WARRANTIES.
// Setup Stream client.
let client = Client(apiKey: "<#ApiKey#>", appId: "<#AppId#>", token: "<#Token#>")
// Create Chris's user feed.
let chrisFeed = client.flatFeed(feedSlug: "user", userId: "chris")
// Create an Activity. You can make own Activity class or struct with custom properties.
let activity = Activity(actor: "chris", verb: "add", object: "picture:10", foreignId: "picture:10")
chrisFeed.add(activity) { result in
// A result of the adding of the activity.
print(result)
}
// Create a following relationship between Jack's "timeline" feed and Chris' "user" feed:
let jackFeed = client.flatFeed(feedSlug: "timeline", userId: "jack")
jackFeed.follow(toTarget: chrisFeed.feedId, activityCopyLimit: 1) { result in
print(result)
}
// Read Jack's timeline and Chris' post appears in the feed:
jackFeed.get(typeOf: Activity.self, pagination: .limit(10)) { result in
let response = try! result.get()
print(response.results)
}
// Remove an activity by referencing it's foreignId
chrisFeed.remove(foreignId: "picture:10") { result in
print(result)
}let user1 = client.flatFeed(feedSlug: "user", userId: "1")
let activity = Activity(actor: "User:1", verb: "pin", object: "Place:42")
user1.add(activity) { result in
if case .success(let activity) = result {
// Added activity
print(activity.id)
}
}// Create a custom Activity class.
final class ExerciseActivity: Activity {
private enum CodingKeys: String, CodingKey {
case course
case participants
case startDate = "started_at"
}
var course: Course
var participants: [String] = []
var startDate: Date = Date()
init(actor: String,
verb: Verb,
object: String,
course: Course,
participants: [String],
startDate: Date = Date()) {
super.init(actor: actor, verb: verb, object: object)
self.course = course
self.participants = participants
self.startDate = startDate
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
course = try container.decode(Course.self, forKey: .course)
participants = try container.decode([String].self, forKey: .participants)
startDate = try container.decode(Date.self, forKey: .startDate)
try super.init(from: decoder)
}
override public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(course, forKey: .course)
try container.encode(participants, forKey: .participants)
try container.encode(startDate, forKey: .startDate)
try super.encode(to: encoder)
}
}
struct Course: Codable {
let name: String
let distance: Float
}
let exerciseActivity = ExerciseActivity(actor: "User:1",
verb: "run",
object: "Exercise:42",
course: Course(name: "Golden Gate Park", distance: 10),
participants: ["Thierry", "Tommaso"])
// Add the exercise activity to the `user1` feed.
user1.add(exerciseActivity) { result in
print(result)
}
// Get a list of exercise activities.
user1.get(typeOf: ExerciseActivity.self) { result in
print(result)
}// Get activities from 5 to 10.
user1.get(pagination: .limit(5) + .offset(5)) { result in /* ... */ }
// Get the 5 activities added after lastActivity.
user1.get(pagination: .limit(5) + .lessThan(lastActivity.id)) { result in /* ... */ }
// Get the 5 activities added before lastActivity.
user1.get(pagination: .limit(5) + .greaterThan(lastActivity.id)) { result in /* ... */ }
// Get activities sorted by rank (Ranked Feeds Enabled).
user1.get(pagination: .limit(5), ranking: "popularity") { result in /* ... */ }
// Get the 5 activities and enrich them with reactions and collections.
user1.get(enrich: true, pagination: .limit(5), includeReactions: [.own, .latest, .counts]) { result in /* ... */ }// Remove an activity by its id.
user1.remove(activityId: "50539e71-d6bf-422d-ad21-c8717df0c325")
// Remove activities foreign_id "run:1".
user1.remove(foreignId: "run:1")// Create a custom activity with a `popularity` property.
let activity = Activity(actor: "1", verb: "like", object: "3", popularity: 100)
user1.add(activity) { _ in
activity.popularity = 10
client.update(activities: [activity]) { result in /* ... */ }
}client.updateActivity(typeOf: ProductActivity.self,
setProperties: ["product.price": 19.99,
"shares": ["facebook": "...", "twitter": "..."]],
unsetProperties: ["daily_likes", "popularity"],
activityId: "54a60c1e-4ee3-494b-a1e3-50c06acb5ed4") { result in /* ... */ }
client.updateActivity(typeOf: ProductActivity.self,
setProperties: [...],
unsetProperties: [...],
foreignId: "product:123",
time: "2016-11-10T13:20:00.000000".streamDate!) { result in /* ... */ }let firstActivity = Activity(actor: "1", verb: "add", object: "1", foreignId: "activity_1", time: Date())
// Add activity to activity feed:
var firstActivityId: String?
user1.add(firstActivity) { result in
let addedActivity = try! result.get()
firstActivityId = addedActivity.id
}
let secondActivity = Activity(actor: "1", verb: "add", object: "1", foreignId: "activity_2", time: Date())
var secondActivityId: String?
user1.add(secondActivity) { result in
let addedActivity = try! result.get()
secondActivityId = addedActivity.id
}
/// The unique combination of `foreignId` and `time` ensure that both
/// activities are unique and therefore the `firstActivityId != secondActivityId`let timelineFeed1 = client.flatFeed(feedSlug: "timeline", userId: "timeline_feed_1"))
// `timeline:timeline_feed_1` follows `user:user_42`:
timelineFeed1.follow(toTarget: FeedId(feedSlug: "user", userId: "user_42")) { result in /* ... */ }
// Follow feed without copying the activities:
timelineFeed1.follow(toTarget: FeedId(feedSlug: "user", userId: "user_42"), activityCopyLimit: 0) { result in /* ... */ }// Stop following feed user_42 - purging history:
timelineFeed1.unfollow(fromTarget: FeedId(feedSlug: "user", userId: "user_42")) { result in /* ... */ }
// Stop following feed user_42 but keep history of activities:
timelineFeed1.unfollow(fromTarget: FeedId(feedSlug: "user", userId: "user_42"), keepHistory: true) { result in /* ... */ }// List followers
user1.followers(offset: 10, limit: 10) { result in /* ... */ }// Retrieve last 10 feeds followed by user_feed_1
user1.following(limit: 10) { result in /* ... */ }
// Retrieve 10 feeds followed by user_feed_1 starting from the 11th
user1.following(offset: 10, limit: 10) { result in /* ... */ }
// Check if user1 follows specific feeds
user1.following(filter: [FeedId(feedSlug: "user", userId: "42"),
FeedId(feedSlug: "user", userId: "43")], limit: 2) { result in /* ... */ }// Mark all activities in the feed as seen:
notificationFeed.get(markOption: .seenAll) { result in /* ... */ }
// Mark some activities as read via specific Activity Group Ids:
notificationFeed.get(markOption: .read(["activityGroupIdOne", "activityGroupIdTwo"]) { result in /* ... */ }Setup a class PopularityActivity
final class PopularityActivity: Activity {
private enum CodingKeys: String, CodingKey {
case popularity
}
var popularity: Int
init(actor: String, verb: Verb, object: String, popularity: Int) {
super.init(actor: actor, verb: verb, object: object)
self.popularity = popularity
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
popularity = try container.decode(Int.self, forKey: .popularity)
try super.init(from: decoder)
}
override public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(popularity, forKey: .popularity)
try super.encode(to: encoder)
}
}// Add Activity.
let activity = PopularityActivity(actor: "User:2", verb: "pin", object: "Place:42", popularity: 5)
user1.add(activity) { result in /* ... */ }// Get activities sorted by the ranking method labelled "activity_popularity" (Ranked Feeds Enabled):
user1.get(ranking: "activity_popularity")// Add the activity to Eric's feed and to Jessica's notification feed:
let activity = TweetActivity(actor: "user:Eric",
verb: "tweet",
object: "tweet:id",
feedIds: [FeedId(feedSlug: "notification", userId: "Jessica")],
message: "@Jessica check out getstream.io it's awesome!")
userFeed1.add(activity) { result in /* ... */ }
// In production use user ids, not their usernames.// The TO field ensures the activity is send to the player, match and team feed
let activity = MatchActivity(actor: "Player:Suarez",
verb: "foul",
object: "Player:Ramos",
match: Match(name: "El Clasico", id: 10),
feedIds: [FeedId(feedSlug: "team", userId: "barcelona"),
FeedId(feedSlug: "match", userId: "1")])
playerFeed1.add(activity) { result in /* ... */ }// retrieve two activities by ID:
client.get(typeOf: Activity.self, activityIds: ["01b3c1dd-e7ab-4649-b5b3-b4371d8f7045",
"ed2837a6-0a3b-4679-adc1-778a1704852d"]) { result in
/* ... */
}
// retrieve an activity by foreign ID and time
client.get(typeOf: Activity.self,
foreignIds: ["like:1", "post:2"],
times: ["2018-07-08T14:09:36.000000".streamDate!, "2018-07-09T20:30:40.000000".streamDate!]) { result in
/* ... */
}let notificationFeed = client.flatFeed(feedSlug: "notification", userId: "1")
var subscription: Subscription? = notificationFeed.subscribe(typeOf: Activity.self) { result in /* ... */ }
// Keep `subscription` object until you need realtime updates and then to unsubscribe set it to nil:
subscription = nil// add a like reaction to the activity with id activityId
client.add(reactionTo: activityId, kindOf: "like") { result in /* ... */ }
// adds a comment reaction to the activity with id activityId
client.add(reactionTo: activityId, kindOf: "comment", extraData: Comment(text: "awesome post!"), userTypeOf: User.self) { result in /* ... */ }Here's a complete example:
// first let's read current user's timeline feed and pick one activity
client.flatFeed(feedSlug: "timeline", userId: "mike").get { result in
if let response = try? result.get(), let activity = response.results.first, let activityId = activity.id {
// then let's add a like reaction to that activity
// note: `.like` is a shared extension for `ReactionKind` equal to "like".
client.add(reactionTo: activityId, kindOf: .like) { result in
print(result) // will print a reaction object in the result.
}
}
}// adds a comment reaction to the activity and notifies Thierry's notification feed
client.add(reactionTo: activityId,
kindOf: "comment",
extraData: Comment(text: "awesome post!"),
userTypeOf: User.self,
targetsFeedIds: [FeedId(feedSlug: "notification", userId: "thierry")]) { result in /* ... */ }// read bob's timeline and include most recent reactions to all activities and their total count
client.flatFeed(feedSlug: "timeline", userId: "bob")
.get(includeReactions: [.latest, .counts]) { result in /* ... */ }
// read bob's timeline and include most recent reactions to all activities and her own reactions
client.flatFeed(feedSlug: "timeline", userId: "bob")
.get(includeReactions: [.own, .latest, .counts]) { result in /* ... */ }// retrieve all kind of reactions for an activity
client.reactions(forActivityId: "ed2837a6-0a3b-4679-adc1-778a1704852d") { result in /* ... */ }
// retrieve first 10 likes for an activity
client.reactions(forActivityId: "ed2837a6-0a3b-4679-adc1-778a1704852d",
kindOf: "like",
pagination: .limit(10)) { result in /* ... */ }
// retrieve the next 10 likes using the id_lt param
client.reactions(forActivityId: "ed2837a6-0a3b-4679-adc1-778a1704852d",
kindOf: "like",
pagination: .lessThan("e561de8f-00f1-11e4-b400-0cc47a024be0")) { result in /* ... */ }// add a like reaction to the previously created comment
client.add(reactionToParentReaction: commentReaction, kindOf: "like") { result in /* ... */ }client.update(reactionId: reactionId, extraData: Comment(text: "love it!"), userTypeOf: User.self) { result in /* ... */ }client.delete(reactionId: reactionId) { result in /* ... */ }Setup a class Food of CollectionObject
final class Food: CollectionObject {
private enum CodingKeys: String, CodingKey {
case name
case rating
}
var name: String
var rating: Float
init(name: String, rating: Float, id: String? = nil) {
self.name = name
self.rating = rating
// For example, set the collection name here for all instances of Food.
super.init(collectionName: "food", id: id)
}
required init(from decoder: Decoder) throws {
let dataContainer = try decoder.container(keyedBy: DataCodingKeys.self)
let container = try dataContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .data)
name = try container.decode(String.self, forKey: .name)
rating = try container.decode(Float.self, forKey: .rating)
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var dataContainer = encoder.container(keyedBy: DataCodingKeys.self)
var container = dataContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .data)
try container.encode(name, forKey: .name)
try container.encode(rating, forKey: .rating)
try super.encode(to: encoder)
}
}client.add(collectionObject: Food(name: "Cheese Burger", rating: 4, id: "cheese-burger")) { result in /* ... */ }
// if you don't have an id on your side, just use nil as the ID and Stream will generate a unique ID
client.add(collectionObject: Food(name: "Cheese Burger", rating: 4)) { result in /* ... */ }client.get(typeOf: Food.self, collectionName: "food", collectionObjectId: "cheese-burger") { result in /* ... */ }client.delete(collectionName: "food", collectionObjectId: "cheese-burger") { result in /* ... */ }client.update(collectionObject: Food(name: "Cheese Burger", rating: 1, id: "cheese-burger")) { result in /* ... */ }// first we add our object to the food collection
let cheeseBurger = Food(name: "Cheese Burger", rating: 4, id: "cheese-burger")
// setup an enriched activity type
typealias UserFoodActivity = EnrichedActivity<User, Food, String>
client.add(collectionObject: cheeseBurger) { _ in
// the object returned by .add can be embedded directly inside of an activity
userFeed.add(UserFoodActivity(actor: client.currentUser!, verb: "grill", object: cheeseBurger)) { _ in
// if we now read the feed, the activity we just added will include the entire full object
userFeed.get(typeOf: UserFoodActivity.self) { result in
let activities = try! result.get().results
// we can then update the object and Stream will propagate the change to all activities
cheeseBurger.name = "Amazing Cheese Burger"
client.update(collectionObject: cheeseBurger) { result in /* ... */ }
}
}
}// First create a collection entry with upsert api
let cheeseBurger = Food(name: "Cheese Burger", rating: 4, id: "cheese-burger")
client.add(collectionObject: cheeseBurger) { _ in
// Then create a user
let user = User(id: "john-doe")
client.create(user: user) { _ in
// Since we know their IDs we can create references to both without reading from APIs
// The `CollectionObjectProtocol` and `UserProtocol` conformed to the `Enrichable` protocol.
let cheeseBurgerRef = cheeseBurger.referenceId
let johnDoeRef = user.referenceId
client.flatFeed(feedSlug: "user", userId: "john")
.add(Activity(actor: johnDoeRef, verb: "eat", object: cheeseBurgerRef)) { result in /* ... */ }
}
}let client = Client(apiKey: "<#ApiKey#>", appId: "<#AppId#>", token: "<#Token#>")
let user = User(id: "john-doe")
client.create(user: user) { result in
if let createdUser = try? result.get() {
client.currentUser = createdUser
}
}// create a new user, if the user already exist an error is returned
client.create(user: User(id: "john-doe"), getOrCreate: false) { result in /* ... */ }
// get or create a new user, if the user already exist the user is returned
client.create(user: User(id: "john-doe"), getOrCreate: true) { result in /* ... */ }client.get(userId: "123") { result in /* ... */ }client.delete(userId: "123") { result in /* ... */ }client.update(user: User(id: "john-doe")) { result in /* ... */ }let userFeed = client.flatFeed(feedSlug: "user", userId: "jack")
// setup an enriched activity type with the `Post` as the subclass of `CollectionObject`
typealias UserPostActivity = EnrichedActivity<User, Post, String>
client.create(user: User(id: "jack")) { result in
client.currentUser = try! result.get()
client.add(collectionObject: Post(text: "...", id: "42-ways-to-improve-your-feed")) { _ in
let post = try! result.get()
userFeed.add(UserPostActivity(actor: client.currentUser!, verb: "post", object: post)) { _ in
// if we now read Jack's feed we will get automatically the enriched data
userFeed.get(typeOf: UserPostActivity.self) { result in
print(result)
// we can also update Jack's post and get the new version
// automatically propagated to his feed and its followers
post.text = "new version of the post"
client.update(collectionObject: post) { _ in
userFeed.get(typeOf: UserPostActivity.self) { result in
// jack's feed now has the new version of the data
print(result)
}
}
}
}
}
}// uploading an `UIImage` as PNG data
client.upload(image: File(name: "image.png", pngImage: image)) { result in /* ... */ }
// uploading an `UIImage` as JPEG data
client.upload(image: File(name: "image.jpg", jpegImage: image, compressionQuality: 0.9)) { result in /* ... */ }
// uploading a file
client.upload(file: File(name: "file", data: fileData)) { result in /* ... */ }// deleting an image using the url returned by the APIs
client.delete(imageURL: imageURL) { result in /* ... */ }
// deleting a file using the url returned by the APIs
client.delete(fileURL: fileURL) { result in /* ... */ }// create a 50x50 thumbnail and crop from center.
// `ImageProcess` has the `crop` parameter as `.center` by default.
client.resizeImage(imageProcess: ImageProcess(url: url, resize: .crop, width: 50, height: 50)) { result in /* ... */ }
// create a 50x50 thumbnail using clipping (keeps aspect ratio).
// `ImageProcess` has the `resize` parameter as `.clip` by default.
client.resizeImage(imageProcess: ImageProcess(url: url, width: 50, height: 50)) { result in /* ... */ }client.og(url: URL(string: "https://www.imdb.com/title/tt0117500/")!) { result in
// An `OGResponse` object would be in the result.
print(result)
}