Skip to content

Commit 6e1b1ee

Browse files
committed
Update README.md
1 parent 0fca564 commit 6e1b1ee

File tree

1 file changed

+146
-2
lines changed

1 file changed

+146
-2
lines changed

README.md

Lines changed: 146 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,146 @@
1-
# TechStacksApp
2-
TechStacks iOS App for iPhone and iPad
1+
# TechStacks iOS App
2+
3+
The TechStacks Native iOS App provides a fluid and responsive experience for browsing content on http://techstacks.io on iPhones and iPad devices. Get it now free on the AppStore!
4+
5+
[![TechStacks on AppStore](https://raw.githubusercontent.com/ServiceStack/Assets/master/img/release-notes/techstacks-appstore.png)](https://itunes.apple.com/us/app/techstacks/id965680615?ls=1&mt=8)
6+
7+
This repository contains the complete source code for the TechStacks App which provides a good example to illustrate the ease-of-use and utility of [ServiceStack's new support for Swift and XCode](https://github.com/ServiceStack/ServiceStack/wiki/Swift-Add-ServiceStack-Reference) for quickly building services-rich iOS Apps.
8+
9+
## Technical Overview
10+
11+
All remote Service Calls used by the App are encapsulated into the [AppData.swift](https://github.com/ServiceStackApps/TechStacksApp/blob/master/src/TechStacks/AppData.swift) class and only uses [JsonServiceClient's](https://github.com/ServiceStack/ServiceStack/wiki/Swift-Add-ServiceStack-Reference#jsonserviceclientswift) non-blocking Async API's to ensure a Responsive UI is maintained throughout the App.
12+
13+
Some other useful features and techniquest that helped with development of this App include:
14+
15+
### MVC and Key-Value Observables (KVO)
16+
17+
If you've ever had to implement `INotifyPropertyChanged` in .NET, you'll find the built-in model binding capabilities in iOS/OSX a refreshing alternative thanks to Objective-C's underlying `NSObject` which automatically generates automatic change notifications for its KV-compliant properties. UIKit and Cocoa frameworks both leverage this feature to enable its [Model-View-Controller Pattern](https://developer.apple.com/library/mac/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html).
18+
19+
As keeping UI's updated with Async API callbacks can get unwieldy, we wanted to go through how we're taking advantage of NSObject's KVO support in Service Responses to simplify maintaining dynamic UI's.
20+
21+
### Enable Key-Value Observing in Swift DTO's
22+
23+
Firstly to enable KVO in your Swift DTO's we'll want to have each DTO inherit from `NSObject` which can be done by uncommenting `BaseObject` option in the header comments as seen below:
24+
25+
```
26+
/* Options:
27+
Date: 2015-02-19 22:43:04
28+
Version: 1
29+
BaseUrl: http://techstacks.io
30+
31+
BaseClass: NSObject
32+
...
33+
*/
34+
```
35+
and click the **Update ServiceStack Reference** Menu Option to fetch the updated DTO's.
36+
37+
Then to [enable Key-Value Observing](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-XID_8) just mark the response DTO variables with the `dynamic` modifier, e.g:
38+
39+
```swift
40+
public dynamic var allTiers:[Option] = []
41+
public dynamic var overview:AppOverviewResponse = AppOverviewResponse()
42+
public dynamic var topTechnologies:[TechnologyInfo] = []
43+
public dynamic var allTechnologies:[Technology] = []
44+
public dynamic var allTechnologyStacks:[TechnologyStack] = []
45+
```
46+
47+
Which is all that's needed to allow properties to be observed as they'll automatically issue change notifications when they're populated in the Service response async callbacks, e.g:
48+
49+
```swift
50+
func loadOverview() -> Promise<AppOverviewResponse> {
51+
return client.getAsync(AppOverview())
52+
.then(body:{(r:AppOverviewResponse) -> AppOverviewResponse in
53+
self.overview = r
54+
self.allTiers = r.allTiers
55+
self.topTechnologies = r.topTechnologies
56+
return r
57+
})
58+
}
59+
60+
func loadAllTechnologies() -> Promise<GetAllTechnologiesResponse> {
61+
return client.getAsync(GetAllTechnologies())
62+
.then(body:{(r:GetAllTechnologiesResponse) -> GetAllTechnologiesResponse in
63+
self.allTechnologies = r.results
64+
return r
65+
})
66+
}
67+
68+
func loadAllTechStacks() -> Promise<GetAllTechnologyStacksResponse> {
69+
return client.getAsync(GetAllTechnologyStacks())
70+
.then(body:{(r:GetAllTechnologyStacksResponse) -> GetAllTechnologyStacksResponse in
71+
self.allTechnologyStacks = r.results
72+
return r
73+
})
74+
}
75+
```
76+
77+
### Observing Data Changes
78+
79+
In your [ViewController](https://github.com/ServiceStackApps/TechStacksApp/blob/0fca564e8c06fd1b71f81faee93a2e04c70a219b/src/TechStacks/HomeViewController.swift) have the datasources for your custom views binded to the desired data (which will initially be empty):
80+
81+
```swift
82+
func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
83+
return appData.allTiers.count
84+
}
85+
...
86+
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
87+
return appData.topTechnologies.count
88+
}
89+
```
90+
91+
Then in `viewDidLoad()` [start observing the properties](https://github.com/ServiceStack/ServiceStack.Swift/blob/67c5c092b92927702f33b6a0669e3aa1de0e2cdc/apps/TechStacks/TechStacks/HomeViewController.swift#L31) your UI Controls are bound to, e.g:
92+
93+
```swift
94+
override func viewDidLoad() {
95+
...
96+
self.appData.observe(self, properties: ["topTechnologies", "allTiers"])
97+
self.appData.loadOverview()
98+
}
99+
deinit { self.appData.unobserve(self) }
100+
```
101+
102+
In the example code above we're using some custom [KVO helpers](https://github.com/ServiceStackApps/TechStacksApp/blob/0fca564e8c06fd1b71f81faee93a2e04c70a219b/src/TechStacks/AppData.swift#L159-L183) to keep the code required to a minimum.
103+
104+
With the observable bindings in place, the change notifications of your observed properties can be handled by overriding `observeValueForKeyPath()` which passes the name of the property that's changed in the `keyPath` argument that can be used to determine the UI Controls to refresh, e.g:
105+
106+
```swift
107+
override func observeValueForKeyPath(keyPath:String, ofObject object:AnyObject, change:[NSObject:AnyObject],
108+
context: UnsafeMutablePointer<Void>) {
109+
switch keyPath {
110+
case "allTiers":
111+
self.technologyPicker.reloadAllComponents()
112+
case "topTechnologies":
113+
self.tblView.reloadData()
114+
default: break
115+
}
116+
}
117+
```
118+
119+
Now that everything's configured, the observables provide an alternative to manually updating UI elements within async callbacks, instead you can now fire-and-forget your async API's and rely on the pre-configured bindings to automatically update the appropriate UI Controls when their bounded properties are updated, e.g:
120+
121+
```swift
122+
self.appData.loadOverview() //Ignore response and use configured KVO Bindings
123+
```
124+
125+
### Images and Custom Binary Requests
126+
127+
In addition to greatly simplifying Web Service Requests, `JsonServiceClient` also makes it easy to fetch any custom HTTP response like Images and other Binary data using the generic `getData()` and `getDataAsync()` NSData API's. This is used in TechStacks to [maintain a cache of all loaded images](https://github.com/ServiceStackApps/TechStacksApp/blob/0fca564e8c06fd1b71f81faee93a2e04c70a219b/src/TechStacks/AppData.swift#L144), reducing number of HTTP requests and load times when navigating between screens:
128+
129+
```swift
130+
var imageCache:[String:UIImage] = [:]
131+
132+
public func loadImageAsync(url:String) -> Promise<UIImage?> {
133+
if let image = imageCache[url] {
134+
return Promise<UIImage?> { (complete, reject) in complete(image) }
135+
}
136+
137+
return client.getDataAsync(url)
138+
.then(body: { (data:NSData) -> UIImage? in
139+
if let image = UIImage(data:data) {
140+
self.imageCache[url] = image
141+
return image
142+
}
143+
return nil
144+
})
145+
}
146+
```

0 commit comments

Comments
 (0)