├── .gitignore
├── .gitmodules
├── Default-568h@2x.png
├── README.md
├── RSOAuthEngine
├── RSOAuthEngine.h
└── RSOAuthEngine.m
├── TwitterDemo.xcodeproj
├── project.pbxproj
└── project.xcworkspace
│ └── contents.xcworkspacedata
├── TwitterDemo
├── AppDelegate.h
├── AppDelegate.m
├── Twitter
│ ├── RSTwitterEngine.h
│ └── RSTwitterEngine.m
├── TwitterDemo-Info.plist
├── TwitterDemo-Prefix.pch
├── ViewController.h
├── ViewController.m
├── WebViewController.h
├── WebViewController.m
├── en.lproj
│ ├── InfoPlist.strings
│ ├── ViewController.xib
│ └── WebViewController.xib
└── main.m
├── logo_cube_114.png
├── logo_cube_57.png
└── screenshot.png
/.gitignore:
--------------------------------------------------------------------------------
1 | *.xcodeproj/*
2 | !*.xcodeproj/project.pbxproj
3 | !*.xcodeproj/project.xcworkspace
4 | *.xcworkspace/*
5 | !*.xcworkspace/contents.xcworkspacedata
6 | /build
7 | *.pbxuser
8 | *.mode1v3
9 | *.mode2v3
10 | *.perspectivev3
11 | target/*
12 | .DS_Store
13 | profile
14 | *.svn
15 | /.svn/*
16 | xcuserdata
17 | Thumbs.db
18 | *.xcworkspacedata
19 | *.xccheckout
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "MKNetworkKit"]
2 | path = MKNetworkKit
3 | url = git://github.com/MugunthKumar/MKNetworkKit.git
4 |
--------------------------------------------------------------------------------
/Default-568h@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rsieiro/RSOAuthEngine/ba43ddb52601df819b37e0e1a7257ca9d9ff815a/Default-568h@2x.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RSOAuthEngine
2 | By Rodrigo Sieiro - [@cheapo](http://twitter.com/cheapo)
3 | [http://rodrigo.sharpcube.com](http://rodrigo.sharpcube.com)
4 |
5 | ## About
6 |
7 | **RSOAuthEngine** is an ARC based OAuth engine for [MKNetworkKit](https://github.com/MugunthKumar/MKNetworkKit). It supports OAuth 1.0a and it's fully compatible with MKNetworkKit existing classes, allowing you to simply inherit `RSOAuthEngine` instead of `MKNetworkEngine` to get OAuth support.
8 |
9 | ## Usage
10 |
11 | If you already have a project using MKNetworkKit, just add the contents of the `RSOAuthEngine` directory to your project and change all classes that inherit from `MKNetworkEngine` to inherit from `RSOAuthEngine` instead. Whenever you need to send an OAuth signed request, replace calls to `enqueueOperation` with `enqueueSignedOperation`.
12 |
13 | If you're not currently using MKNetworkKit, follow the instructions to add it to your project [here](http://blog.mugunthkumar.com/products/ios-framework-introducing-mknetworkkit/) first, then add **RSOAuthEngine** as written in the previous paragraph. **Important**: although not mentioned in the instructions, MKNetworkKit also requires Security.framework.
14 |
15 | ### Usage Details
16 |
17 | A common OAuth flow using **RSOAuthEngine** should go like this:
18 |
19 | 1. Create a class that inherits from **RSOAuthEngine**.
20 | 2. Init your class using one of the defined initializers that include your Consumer Key and Secret.
21 | 3. Send a signed operation to get a request token.
22 | 4. Fill the request token using `fillTokenWithResponseBody:type` (use `RSOAuthRequestToken` as type).
23 | 5. Redirect the user to the authorization page and wait for the callback.
24 | 6. Fill the request token (again) using `fillTokenWithResponseBody:type` (use `RSOAuthRequestToken` as type), this time using the parameters received in the callback.
25 | 7. Send another request to get an access token.
26 | 8. Fill the access token using `fillTokenWithResponseBody:type` (use `RSOAuthAccessToken` as type).
27 | 9. From now on, all requests sent with `enqueueSignedOperation` will be signed with your tokens.
28 |
29 | Alternatively you could use `setAccessToken:secret` after initialization to define a previously stored access token. If you need or want to use xAuth instead of the request token/authorize workflow, please take a look at the Instapaper demo.
30 |
31 | ### XOAuth
32 |
33 | This library also supports generating XOAuth strings (to use with Gmail SMTP and IMAP servers, for example). Just call `generateXOAuthStringForURL:method` with the desired URL and method (GET/POST).
34 |
35 | ## Twitter Demo
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
About
44 |
45 |
This sample project demonstrates how to use RSOAuthEngine to authenticate with Twitter. It includes a basic engine that implements Twitter's OAuth authentication flow and allows you to post a tweet. It also shows you how to persist the OAuth access token in the Keychain. The Twitter engine should not be considered production code, and is only included to demonstrate RSOAuthEngine.
46 |
47 |
Building
48 |
49 |
To build the demo project, follow these steps:
50 |
51 |
52 |
In the project directory, run git submodule update --init to retrieve MKNetworkKit (added to the project as a submodule).
53 |
Put your consumer key and secret at the top of RSTwitterEngine.m and remove the #error macro. If you don't have a consumer key/secret, register an app at https://dev.twitter.com/apps to get a pair. Important: you need to add a dummy callback URL to your app when registering, otherwise Twitter won't allow you to send a callback URL in the OAuth request.
54 |
55 |
56 |
Tips
57 |
58 |
Swipe from left to right in the status message to clear previously stored OAuth tokens.
59 |
60 |
61 |
62 |
63 | ## Compatibility
64 |
65 | Currently this engine has been tested with Twitter and Instapaper. If you use **RSOAuthEngine** to implement OAuth authentication with another service, please let me know so I can update this section.
66 |
67 | ## License
68 |
69 | **RSOAuthEngine** is licensed under the MIT License. Please give me some kind of attribution if you use it in your project, such as a "thanks" note somewhere. I'd also love to know if you use my code, please drop me a line if you do!
70 |
71 | Full license text follows:
72 |
73 | Permission is hereby granted, free of charge, to any person obtaining a copy
74 | of this software and associated documentation files (the "Software"), to deal
75 | in the Software without restriction, including without limitation the rights
76 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
77 | copies of the Software, and to permit persons to whom the Software is
78 | furnished to do so, subject to the following conditions:
79 |
80 | The above copyright notice and this permission notice shall be included in
81 | all copies or substantial portions of the Software.
82 |
83 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
84 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
85 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
86 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
87 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
88 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
89 | THE SOFTWARE.
90 |
91 | ## Acknowledgments
92 |
93 | **RSOAuthEngine** may contain code from [ASI-HTTP-Request-OAuth](https://github.com/keybuk/asi-http-request-oauth) by Scott James Remnant and the iPhone version of [OAuthConsumer](https://github.com/jdg/oauthconsumer) by Jonathan George. I used bits and pieces of the code from both projects as references to write this engine.
--------------------------------------------------------------------------------
/RSOAuthEngine/RSOAuthEngine.h:
--------------------------------------------------------------------------------
1 | //
2 | // RSOAuthEngine.h
3 | // RSOAuthEngine
4 | //
5 | // Created by Rodrigo Sieiro on 12/11/11.
6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 |
26 | #import "MKNetworkEngine.h"
27 |
28 | typedef enum _RSOAuthTokenType
29 | {
30 | RSOAuthRequestToken,
31 | RSOAuthRequestAccessToken,
32 | RSOAuthAccessToken,
33 | }
34 | RSOAuthTokenType;
35 |
36 | typedef enum _RSOOAuthSignatureMethod {
37 | RSOAuthPlainText,
38 | RSOAuthHMAC_SHA1,
39 | } RSOAuthSignatureMethod;
40 |
41 | typedef enum _RSOAuthParameterStyle {
42 | RSOAuthParameterStyleHeader,
43 | RSOAuthParameterStylePostBody,
44 | RSOAuthParameterStyleQueryString
45 | } RSOAuthParameterStyle;
46 |
47 | @interface RSOAuthEngine : MKNetworkEngine
48 | {
49 | @private
50 | RSOAuthTokenType _tokenType;
51 | RSOAuthSignatureMethod _signatureMethod;
52 | NSString *_consumerSecret;
53 | NSString *_tokenSecret;
54 | NSString *_callbackURL;
55 | NSString *_verifier;
56 | NSMutableDictionary *_oAuthValues;
57 | NSMutableDictionary *_customValues;
58 | }
59 |
60 | @property (readonly) RSOAuthTokenType tokenType;
61 | @property (readonly) RSOAuthSignatureMethod signatureMethod;
62 | @property (readonly) NSString *consumerKey;
63 | @property (readonly) NSString *consumerSecret;
64 | @property (readonly) NSString *callbackURL;
65 | @property (readonly) NSString *token;
66 | @property (readonly) NSString *tokenSecret;
67 | @property (readonly) NSString *verifier;
68 |
69 | @property (nonatomic, assign) RSOAuthParameterStyle parameterStyle;
70 |
71 | - (id)initWithHostName:(NSString *)hostName
72 | customHeaderFields:(NSDictionary *)headers
73 | signatureMethod:(RSOAuthSignatureMethod)signatureMethod
74 | consumerKey:(NSString *)consumerKey
75 | consumerSecret:(NSString *)consumerSecret
76 | callbackURL:(NSString *)callbackURL;
77 |
78 | - (id)initWithHostName:(NSString *)hostName
79 | customHeaderFields:(NSDictionary *)headers
80 | signatureMethod:(RSOAuthSignatureMethod)signatureMethod
81 | consumerKey:(NSString *)consumerKey
82 | consumerSecret:(NSString *)consumerSecret;
83 |
84 | - (BOOL)isAuthenticated;
85 | - (void)resetOAuthToken;
86 | - (NSString *)customValueForKey:(NSString *)key;
87 | - (void)fillTokenWithResponseBody:(NSString *)body type:(RSOAuthTokenType)tokenType;
88 | - (void)setAccessToken:(NSString *)token secret:(NSString *)tokenSecret;
89 | - (void)signRequest:(MKNetworkOperation *)request;
90 | - (void)enqueueSignedOperation:(MKNetworkOperation *)op;
91 | - (NSString *)generateXOAuthStringForURL:(NSString *)url method:(NSString *)method;
92 |
93 | @end
94 |
--------------------------------------------------------------------------------
/RSOAuthEngine/RSOAuthEngine.m:
--------------------------------------------------------------------------------
1 | //
2 | // RSOAuthEngine.m
3 | // RSOAuthEngine
4 | //
5 | // Created by Rodrigo Sieiro on 12/11/11.
6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 |
26 | #include
27 | #import
28 | #import "NSData+MKBase64.h"
29 | #import "NSString+MKNetworkKitAdditions.h"
30 | #import "RSOAuthEngine.h"
31 |
32 | static const NSString *oauthVersion = @"1.0";
33 |
34 | static const NSString *oauthSignatureMethodName[] = {
35 | @"PLAINTEXT",
36 | @"HMAC-SHA1",
37 | };
38 |
39 | // This category for MKNetworkOperation was added
40 | // Because we need access to these fields
41 | // And they are private inside the class
42 |
43 | @interface MKNetworkOperation (RSO)
44 |
45 | @property (strong, nonatomic) NSMutableURLRequest *request;
46 | @property (strong, nonatomic) NSMutableDictionary *fieldsToBePosted;
47 | @property (strong, nonatomic) NSMutableArray *filesToBePosted;
48 | @property (strong, nonatomic) NSMutableArray *dataToBePosted;
49 |
50 | - (void)rs_setURL:(NSURL *)URL;
51 | - (void)rs_setValue:(NSString *)value forKey:(NSString *)key;
52 |
53 | @end
54 |
55 | @implementation MKNetworkOperation (RSO)
56 |
57 | @dynamic request;
58 | @dynamic fieldsToBePosted;
59 | @dynamic filesToBePosted;
60 | @dynamic dataToBePosted;
61 |
62 | - (void)rs_setURL:(NSURL *)URL
63 | {
64 | [self.request setURL:URL];
65 | }
66 |
67 | - (void)rs_setValue:(NSString *)value forKey:(NSString *)key
68 | {
69 | [self.fieldsToBePosted setObject:value forKey:key];
70 | }
71 |
72 | @end
73 |
74 | @interface RSOAuthEngine ()
75 |
76 | - (NSString *)signatureBaseStringForURL:(NSString *)url method:(NSString *)method parameters:(NSMutableArray *)parameters;
77 | - (NSString *)signatureBaseStringForRequest:(MKNetworkOperation *)request;
78 | - (NSString *)generatePlaintextSignatureFor:(NSString *)baseString;
79 | - (NSString *)generateHMAC_SHA1SignatureFor:(NSString *)baseString;
80 | - (void)addCustomValue:(NSString *)value withKey:(NSString *)key;
81 | - (void)setOAuthValue:(NSString *)value forKey:(NSString *)key;
82 |
83 | @end
84 |
85 | @implementation RSOAuthEngine
86 |
87 | #pragma mark - Read-only Properties
88 |
89 | - (NSString *)consumerKey
90 | {
91 | return (_oAuthValues) ? [_oAuthValues objectForKey:@"oauth_consumer_key"] : @"";
92 | }
93 |
94 | - (NSString *)token
95 | {
96 | return (_oAuthValues) ? [_oAuthValues objectForKey:@"oauth_token"] : @"";
97 | }
98 |
99 | #pragma mark - Initialization
100 |
101 | - (id)initWithHostName:(NSString *)hostName
102 | customHeaderFields:(NSDictionary *)headers
103 | signatureMethod:(RSOAuthSignatureMethod)signatureMethod
104 | consumerKey:(NSString *)consumerKey
105 | consumerSecret:(NSString *)consumerSecret
106 | callbackURL:(NSString *)callbackURL
107 | {
108 | NSAssert(consumerKey, @"Consumer Key cannot be null");
109 | NSAssert(consumerSecret, @"Consumer Secret cannot be null");
110 |
111 | self = [super initWithHostName:hostName customHeaderFields:headers];
112 |
113 | if (self) {
114 | _consumerSecret = consumerSecret;
115 | _callbackURL = callbackURL;
116 | _signatureMethod = signatureMethod;
117 |
118 | _oAuthValues = [@{
119 | @"oauth_version": oauthVersion,
120 | @"oauth_signature_method": oauthSignatureMethodName[_signatureMethod],
121 | @"oauth_consumer_key": consumerKey,
122 | @"oauth_token": @"",
123 | @"oauth_verifier": @"",
124 | @"oauth_callback": @"",
125 | @"oauth_signature": @"",
126 | @"oauth_timestamp": @"",
127 | @"oauth_nonce": @"",
128 | @"realm": @""
129 | } mutableCopy];
130 |
131 | [self resetOAuthToken];
132 |
133 | // By default, add the OAuth parameters to the Authorization header
134 | self.parameterStyle = RSOAuthParameterStyleHeader;
135 | }
136 |
137 | return self;
138 | }
139 |
140 | - (id)initWithHostName:(NSString *)hostName
141 | customHeaderFields:(NSDictionary *)headers
142 | signatureMethod:(RSOAuthSignatureMethod)signatureMethod
143 | consumerKey:(NSString *)consumerKey
144 | consumerSecret:(NSString *)consumerSecret
145 | {
146 | return [self initWithHostName:hostName
147 | customHeaderFields:headers
148 | signatureMethod:signatureMethod
149 | consumerKey:consumerKey
150 | consumerSecret:consumerSecret
151 | callbackURL:nil];
152 | }
153 |
154 | #pragma mark - OAuth Signature Generators
155 |
156 | - (NSString *)signatureBaseStringForURL:(NSString *)url method:(NSString *)method parameters:(NSMutableArray *)parameters
157 | {
158 | // Create a NSMutableArray if not created yet
159 | if (!parameters) {
160 | parameters = [NSMutableArray arrayWithCapacity:[_oAuthValues count]];
161 | }
162 |
163 | // Add parameters from the OAuth header
164 | [_oAuthValues enumerateKeysAndObjectsUsingBlock:^(id key, NSString *obj, BOOL *stop) {
165 | if ([key hasPrefix:@"oauth_"] && ![key isEqualToString:@"oauth_signature"] && obj && ![obj isEqualToString:@""]) {
166 | [parameters addObject:@{
167 | @"key": [key mk_urlEncodedString],
168 | @"value": [obj mk_urlEncodedString]
169 | }];
170 | }
171 | }];
172 |
173 | // Sort by name and value
174 | [parameters sortUsingComparator:^(id obj1, id obj2) {
175 | NSDictionary *val1 = obj1, *val2 = obj2;
176 | NSComparisonResult result = [[val1 objectForKey:@"key"] compare:[val2 objectForKey:@"key"] options:NSLiteralSearch];
177 | if (result != NSOrderedSame) return result;
178 | return [[val1 objectForKey:@"value"] compare:[val2 objectForKey:@"value"] options:NSLiteralSearch];
179 | }];
180 |
181 | // Join sorted components
182 | NSMutableArray *normalizedParameters = [NSMutableArray array];
183 | [parameters enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) {
184 | [normalizedParameters addObject:[NSString stringWithFormat:@"%@=%@", [obj objectForKey:@"key"], [obj objectForKey:@"value"]]];
185 | }];
186 |
187 | // Create the signature base string
188 | NSString *signatureBaseString = [NSString stringWithFormat:@"%@&%@&%@",
189 | [method uppercaseString],
190 | [url mk_urlEncodedString],
191 | [[normalizedParameters componentsJoinedByString:@"&"] mk_urlEncodedString]];
192 |
193 | return signatureBaseString;
194 | }
195 |
196 | - (NSString *)signatureBaseStringForRequest:(MKNetworkOperation *)request
197 | {
198 | NSMutableArray *parameters = [NSMutableArray array];
199 | NSURL *url = [NSURL URLWithString:request.url];
200 |
201 | // Get the base URL String (with no parameters)
202 | NSArray *urlParts = [request.url componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"?#"]];
203 | NSString *baseURL = [urlParts objectAtIndex:0];
204 |
205 | // Only include GET and POST fields if there are no files or data to be posted
206 | if ([request.filesToBePosted count] == 0 && [request.dataToBePosted count] == 0) {
207 | // Add parameters from the query string
208 | NSArray *pairs = [url.query componentsSeparatedByString:@"&"];
209 | [pairs enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) {
210 | NSArray *elements = [obj componentsSeparatedByString:@"="];
211 | NSString *key = [elements objectAtIndex:0];
212 | NSString *value = (elements.count > 1) ? [elements objectAtIndex:1] : @"";
213 |
214 | [parameters addObject:@{@"key": key, @"value": value}];
215 | }];
216 |
217 | // Add parameters from the request body
218 | // Only if we're POSTing, GET parameters were already added
219 | if ([[[request HTTPMethod] uppercaseString] isEqualToString:@"POST"] && [request postDataEncoding] == MKNKPostDataEncodingTypeURL) {
220 | [request.readonlyPostDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
221 | if([obj isKindOfClass:[NSString class]]) {
222 | [parameters addObject:@{
223 | @"key": [key mk_urlEncodedString],
224 | @"value": [obj mk_urlEncodedString]
225 | }];
226 | } else {
227 | [parameters addObject:@{
228 | @"key": [key mk_urlEncodedString],
229 | @"value": [NSString stringWithFormat:@"%@", obj]
230 | }];
231 | }
232 | }];
233 | }
234 | }
235 |
236 | return [self signatureBaseStringForURL:baseURL method:[request HTTPMethod] parameters:parameters];
237 | }
238 |
239 | - (NSString *)generatePlaintextSignatureFor:(NSString *)baseString
240 | {
241 | return [NSString stringWithFormat:@"%@&%@",
242 | self.consumerSecret != nil ? [self.consumerSecret mk_urlEncodedString] : @"",
243 | self.tokenSecret != nil ? [self.tokenSecret mk_urlEncodedString] : @""];
244 | }
245 |
246 | - (NSString *)generateHMAC_SHA1SignatureFor:(NSString *)baseString
247 | {
248 | NSString *key = [self generatePlaintextSignatureFor:baseString];
249 |
250 | const char *keyBytes = [key cStringUsingEncoding:NSUTF8StringEncoding];
251 | const char *baseStringBytes = [baseString cStringUsingEncoding:NSUTF8StringEncoding];
252 | unsigned char digestBytes[CC_SHA1_DIGEST_LENGTH];
253 |
254 | CCHmacContext ctx;
255 | CCHmacInit(&ctx, kCCHmacAlgSHA1, keyBytes, strlen(keyBytes));
256 | CCHmacUpdate(&ctx, baseStringBytes, strlen(baseStringBytes));
257 | CCHmacFinal(&ctx, digestBytes);
258 |
259 | NSData *digestData = [NSData dataWithBytes:digestBytes length:CC_SHA1_DIGEST_LENGTH];
260 | return [digestData base64EncodedString];
261 | }
262 |
263 | #pragma mark - Dictionary Helpers
264 |
265 | - (void)addCustomValue:(NSString *)value withKey:(NSString *)key
266 | {
267 | if (!_customValues) _customValues = [[NSMutableDictionary alloc] initWithCapacity:1];
268 |
269 | if (value) {
270 | [_customValues setObject:value forKey:key];
271 | } else {
272 | [_customValues setObject:@"" forKey:key];
273 | }
274 | }
275 |
276 | - (void)setOAuthValue:(NSString *)value forKey:(NSString *)key
277 | {
278 | if (value) {
279 | [_oAuthValues setObject:value forKey:key];
280 | } else {
281 | [_oAuthValues setObject:@"" forKey:key];
282 | }
283 | }
284 |
285 | #pragma mark - Public Methods
286 |
287 | - (BOOL)isAuthenticated
288 | {
289 | return (_tokenType == RSOAuthAccessToken && self.token && self.tokenSecret);
290 | }
291 |
292 | - (void)resetOAuthToken
293 | {
294 | _tokenType = RSOAuthRequestToken;
295 | _tokenSecret = nil;
296 | _verifier = nil;
297 | _customValues = nil;
298 |
299 | [self setOAuthValue:self.callbackURL forKey:@"oauth_callback"];
300 | [self setOAuthValue:@"" forKey:@"oauth_verifier"];
301 | [self setOAuthValue:@"" forKey:@"oauth_token"];
302 | }
303 |
304 | - (NSString *)customValueForKey:(NSString *)key
305 | {
306 | if (!_customValues) return nil;
307 | return [_customValues objectForKey:key];
308 | }
309 |
310 | - (void)fillTokenWithResponseBody:(NSString *)body type:(RSOAuthTokenType)tokenType
311 | {
312 | NSArray *pairs = [body componentsSeparatedByString:@"&"];
313 |
314 | [pairs enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) {
315 | NSArray *elements = [obj componentsSeparatedByString:@"="];
316 | NSString *key = [[elements objectAtIndex:0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
317 | NSString *value = [[elements objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
318 |
319 | if ([key isEqualToString:@"oauth_token"]) {
320 | [self setOAuthValue:value forKey:@"oauth_token"];
321 | } else if ([key isEqualToString:@"oauth_token_secret"]) {
322 | _tokenSecret = value;
323 | } else if ([key isEqualToString:@"oauth_verifier"]) {
324 | _verifier = value;
325 | } else {
326 | [self addCustomValue:value withKey:key];
327 | }
328 | }];
329 |
330 | _tokenType = tokenType;
331 |
332 | // If we already have an Access Token, no need to send the Verifier and Callback URL
333 | if (_tokenType == RSOAuthAccessToken) {
334 | [self setOAuthValue:nil forKey:@"oauth_callback"];
335 | [self setOAuthValue:nil forKey:@"oauth_verifier"];
336 | } else if (_tokenType == RSOAuthRequestAccessToken) {
337 | [self setOAuthValue:nil forKey:@"oauth_callback"];
338 | [self setOAuthValue:self.verifier forKey:@"oauth_verifier"];
339 | } else {
340 | [self setOAuthValue:self.callbackURL forKey:@"oauth_callback"];
341 | [self setOAuthValue:self.verifier forKey:@"oauth_verifier"];
342 | }
343 | }
344 |
345 | - (void)setAccessToken:(NSString *)token secret:(NSString *)tokenSecret
346 | {
347 | NSAssert(token, @"Token cannot be null");
348 | NSAssert(tokenSecret, @"Token Secret cannot be null");
349 |
350 | [self resetOAuthToken];
351 |
352 | [self setOAuthValue:token forKey:@"oauth_token"];
353 | _tokenSecret = tokenSecret;
354 | _tokenType = RSOAuthAccessToken;
355 |
356 | // Since we already have an Access Token, no need to send the Verifier and Callback URL
357 | [self setOAuthValue:nil forKey:@"oauth_callback"];
358 | [self setOAuthValue:nil forKey:@"oauth_verifier"];
359 | }
360 |
361 | - (void)signRequest:(MKNetworkOperation *)request
362 | {
363 | NSAssert(_oAuthValues && self.consumerKey && self.consumerSecret, @"Please use an initializer with Consumer Key and Consumer Secret.");
364 |
365 | // Generate timestamp and nonce values
366 | [self setOAuthValue:[NSString stringWithFormat:@"%ld", time(NULL)] forKey:@"oauth_timestamp"];
367 | [self setOAuthValue:[NSString uniqueString] forKey:@"oauth_nonce"];
368 |
369 | // Construct the signature base string
370 | NSString *baseString = [self signatureBaseStringForRequest:request];
371 |
372 | // Generate the signature
373 | switch (_signatureMethod) {
374 | case RSOAuthHMAC_SHA1:
375 | [self setOAuthValue:[self generateHMAC_SHA1SignatureFor:baseString] forKey:@"oauth_signature"];
376 | break;
377 | default:
378 | [self setOAuthValue:[self generatePlaintextSignatureFor:baseString] forKey:@"oauth_signature"];
379 | break;
380 | }
381 |
382 | if (self.parameterStyle == RSOAuthParameterStyleHeader) {
383 | NSMutableArray *oauthHeaders = [NSMutableArray array];
384 |
385 | [_oAuthValues enumerateKeysAndObjectsUsingBlock:^(id key, NSString *obj, BOOL *stop) {
386 | if (obj && ![obj isEqualToString:@""]) {
387 | [oauthHeaders addObject:[NSString stringWithFormat:@"%@=\"%@\"", [key mk_urlEncodedString], [obj mk_urlEncodedString]]];
388 | }
389 | }];
390 |
391 | // Set the Authorization header
392 | NSString *oauthData = [NSString stringWithFormat:@"OAuth %@", [oauthHeaders componentsJoinedByString:@", "]];
393 | NSDictionary *oauthHeader = @{@"Authorization": oauthData};
394 |
395 | [request addHeaders:oauthHeader];
396 | } else if (self.parameterStyle == RSOAuthParameterStylePostBody && [request.readonlyRequest.HTTPMethod caseInsensitiveCompare:@"GET"] != NSOrderedSame) {
397 | [_oAuthValues enumerateKeysAndObjectsUsingBlock:^(id key, NSString *obj, BOOL *stop) {
398 | if (obj && ![obj isEqualToString:@""]) {
399 | [request rs_setValue:obj forKey:key];
400 | }
401 | }];
402 | } else { // self.parameterStyle == RSOAuthParameterStyleQueryString
403 | NSMutableArray *oauthParams = [NSMutableArray array];
404 |
405 | // Fill the authorization header array
406 | [_oAuthValues enumerateKeysAndObjectsUsingBlock:^(id key, NSString *obj, BOOL *stop) {
407 | if (obj && ![obj isEqualToString:@""]) {
408 | [oauthParams addObject:[NSString stringWithFormat:@"%@=%@", [key mk_urlEncodedString], [obj mk_urlEncodedString]]];
409 | }
410 | }];
411 |
412 | NSString *url = request.url;
413 | NSString *separator = [url rangeOfString:@"?"].length > 0 ? @"&" : @"?";
414 | url = [NSString stringWithFormat:@"%@%@%@", url, separator, [oauthParams componentsJoinedByString:@"&"]];
415 | [request rs_setURL:[NSURL URLWithString:url]];
416 | }
417 | }
418 |
419 | - (void)enqueueSignedOperation:(MKNetworkOperation *)op
420 | {
421 | // Sign and Enqueue the operation
422 | [self signRequest:op];
423 | [self enqueueOperation:op];
424 | }
425 |
426 | - (NSString *)generateXOAuthStringForURL:(NSString *)url method:(NSString *)method
427 | {
428 | NSAssert(_oAuthValues && self.consumerKey && self.consumerSecret, @"Please use an initializer with Consumer Key and Consumer Secret.");
429 |
430 | // Generate timestamp and nonce values
431 | [self setOAuthValue:[NSString stringWithFormat:@"%ld", time(NULL)] forKey:@"oauth_timestamp"];
432 | [self setOAuthValue:[NSString uniqueString] forKey:@"oauth_nonce"];
433 |
434 | // Construct the signature base string
435 | NSString *baseString = [self signatureBaseStringForURL:url method:method parameters:nil];
436 |
437 | // Generate the signature
438 | switch (_signatureMethod) {
439 | case RSOAuthHMAC_SHA1:
440 | [self setOAuthValue:[self generateHMAC_SHA1SignatureFor:baseString] forKey:@"oauth_signature"];
441 | break;
442 | default:
443 | [self setOAuthValue:[self generatePlaintextSignatureFor:baseString] forKey:@"oauth_signature"];
444 | break;
445 | }
446 |
447 | NSMutableArray *oauthHeaders = [NSMutableArray array];
448 |
449 | // Fill the authorization header array
450 | [_oAuthValues enumerateKeysAndObjectsUsingBlock:^(id key, NSString *obj, BOOL *stop) {
451 | if (obj && ![obj isEqualToString:@""]) {
452 | [oauthHeaders addObject:[NSString stringWithFormat:@"%@=\"%@\"", [key mk_urlEncodedString], [obj mk_urlEncodedString]]];
453 | }
454 | }];
455 |
456 | // Set the XOAuth String
457 | NSString *xOAuthString = [NSString stringWithFormat:@"%@ %@ %@", [method uppercaseString], url, [oauthHeaders componentsJoinedByString:@","]];
458 |
459 | // Base64-encode the string with no line wrap
460 | size_t outputLength;
461 | NSData *stringData = [xOAuthString dataUsingEncoding:NSUTF8StringEncoding];
462 | char *outputBuffer = mk_NewBase64Encode([stringData bytes], [stringData length], false, &outputLength);
463 | NSString *finalString = [[NSString alloc] initWithBytes:outputBuffer length:outputLength encoding:NSASCIIStringEncoding];
464 | free(outputBuffer);
465 |
466 | return finalString;
467 | }
468 |
469 | @end
470 |
--------------------------------------------------------------------------------
/TwitterDemo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 5B41240E19129582001F575F /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 5B41240D19129582001F575F /* Default-568h@2x.png */; };
11 | 5B412410191295DE001F575F /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B41240F191295DE001F575F /* ImageIO.framework */; };
12 | 5B9770C8149789C200791728 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B9770C7149789C200791728 /* UIKit.framework */; };
13 | 5B9770CA149789C200791728 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B9770C9149789C200791728 /* Foundation.framework */; };
14 | 5B9770CC149789C200791728 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B9770CB149789C200791728 /* CoreGraphics.framework */; };
15 | 5B9770D2149789C200791728 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5B9770D0149789C200791728 /* InfoPlist.strings */; };
16 | 5B9770D4149789C200791728 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B9770D3149789C200791728 /* main.m */; };
17 | 5B9770D8149789C200791728 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B9770D7149789C200791728 /* AppDelegate.m */; };
18 | 5B9770DB149789C200791728 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B9770DA149789C200791728 /* ViewController.m */; };
19 | 5B9770DE149789C200791728 /* ViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B9770DC149789C200791728 /* ViewController.xib */; };
20 | 5B97710614978D5400791728 /* NSDate+RFC1123.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B9770F614978D5400791728 /* NSDate+RFC1123.m */; };
21 | 5B97710714978D5400791728 /* NSDictionary+RequestEncoding.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B9770F814978D5400791728 /* NSDictionary+RequestEncoding.m */; };
22 | 5B97710814978D5400791728 /* NSString+MKNetworkKitAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B9770FA14978D5400791728 /* NSString+MKNetworkKitAdditions.m */; };
23 | 5B97710914978D5400791728 /* UIAlertView+MKNetworkKitAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B9770FC14978D5400791728 /* UIAlertView+MKNetworkKitAdditions.m */; };
24 | 5B97710A14978D5400791728 /* MKNetworkEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B9770FE14978D5400791728 /* MKNetworkEngine.m */; };
25 | 5B97710B14978D5400791728 /* MKNetworkOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B97710114978D5400791728 /* MKNetworkOperation.m */; };
26 | 5B97710C14978D5400791728 /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B97710414978D5400791728 /* Reachability.m */; };
27 | 5B97710E14978DB400791728 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B97710D14978DB400791728 /* CFNetwork.framework */; };
28 | 5B97711014978DBC00791728 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B97710F14978DBC00791728 /* SystemConfiguration.framework */; };
29 | 5B97711214978F6100791728 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B97711114978F6100791728 /* Security.framework */; };
30 | 5B97711A14978FFD00791728 /* RSOAuthEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B97711814978FFD00791728 /* RSOAuthEngine.m */; };
31 | 5B97711D1497911300791728 /* WebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B97711C1497911300791728 /* WebViewController.m */; };
32 | 5B9771201497912A00791728 /* WebViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B97711E1497912A00791728 /* WebViewController.xib */; };
33 | 5B9771241497927C00791728 /* RSTwitterEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B9771231497927C00791728 /* RSTwitterEngine.m */; };
34 | 5BAA64D61497D8CB00ED569D /* logo_cube_57.png in Resources */ = {isa = PBXBuildFile; fileRef = 5BAA64D51497D8CB00ED569D /* logo_cube_57.png */; };
35 | 5BAA64D81497D8D300ED569D /* logo_cube_114.png in Resources */ = {isa = PBXBuildFile; fileRef = 5BAA64D71497D8D300ED569D /* logo_cube_114.png */; };
36 | CD6AB1CD169D2F5100D2AD3F /* NSData+MKBase64.m in Sources */ = {isa = PBXBuildFile; fileRef = CD6AB1CC169D2F5100D2AD3F /* NSData+MKBase64.m */; };
37 | /* End PBXBuildFile section */
38 |
39 | /* Begin PBXFileReference section */
40 | 5B41240D19129582001F575F /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; };
41 | 5B41240F191295DE001F575F /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = System/Library/Frameworks/ImageIO.framework; sourceTree = SDKROOT; };
42 | 5B9770C3149789C200791728 /* TwitterDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TwitterDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
43 | 5B9770C7149789C200791728 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
44 | 5B9770C9149789C200791728 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
45 | 5B9770CB149789C200791728 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
46 | 5B9770CF149789C200791728 /* TwitterDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "TwitterDemo-Info.plist"; sourceTree = ""; };
47 | 5B9770D1149789C200791728 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; };
48 | 5B9770D3149789C200791728 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = main.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
49 | 5B9770D5149789C200791728 /* TwitterDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TwitterDemo-Prefix.pch"; sourceTree = ""; };
50 | 5B9770D6149789C200791728 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = AppDelegate.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
51 | 5B9770D7149789C200791728 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = AppDelegate.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
52 | 5B9770D9149789C200791728 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ViewController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
53 | 5B9770DA149789C200791728 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ViewController.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
54 | 5B9770DD149789C200791728 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/ViewController.xib; sourceTree = ""; };
55 | 5B9770F514978D5400791728 /* NSDate+RFC1123.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate+RFC1123.h"; sourceTree = ""; };
56 | 5B9770F614978D5400791728 /* NSDate+RFC1123.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+RFC1123.m"; sourceTree = ""; };
57 | 5B9770F714978D5400791728 /* NSDictionary+RequestEncoding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+RequestEncoding.h"; sourceTree = ""; };
58 | 5B9770F814978D5400791728 /* NSDictionary+RequestEncoding.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+RequestEncoding.m"; sourceTree = ""; };
59 | 5B9770F914978D5400791728 /* NSString+MKNetworkKitAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+MKNetworkKitAdditions.h"; sourceTree = ""; };
60 | 5B9770FA14978D5400791728 /* NSString+MKNetworkKitAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+MKNetworkKitAdditions.m"; sourceTree = ""; };
61 | 5B9770FB14978D5400791728 /* UIAlertView+MKNetworkKitAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIAlertView+MKNetworkKitAdditions.h"; sourceTree = ""; };
62 | 5B9770FC14978D5400791728 /* UIAlertView+MKNetworkKitAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIAlertView+MKNetworkKitAdditions.m"; sourceTree = ""; };
63 | 5B9770FD14978D5400791728 /* MKNetworkEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MKNetworkEngine.h; sourceTree = ""; };
64 | 5B9770FE14978D5400791728 /* MKNetworkEngine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MKNetworkEngine.m; sourceTree = ""; };
65 | 5B9770FF14978D5400791728 /* MKNetworkKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MKNetworkKit.h; sourceTree = ""; };
66 | 5B97710014978D5400791728 /* MKNetworkOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MKNetworkOperation.h; sourceTree = ""; };
67 | 5B97710114978D5400791728 /* MKNetworkOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MKNetworkOperation.m; sourceTree = ""; };
68 | 5B97710314978D5400791728 /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = ""; };
69 | 5B97710414978D5400791728 /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Reachability.m; sourceTree = ""; };
70 | 5B97710D14978DB400791728 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; };
71 | 5B97710F14978DBC00791728 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
72 | 5B97711114978F6100791728 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
73 | 5B97711714978FFD00791728 /* RSOAuthEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RSOAuthEngine.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
74 | 5B97711814978FFD00791728 /* RSOAuthEngine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = RSOAuthEngine.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
75 | 5B97711B1497911300791728 /* WebViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = WebViewController.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
76 | 5B97711C1497911300791728 /* WebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebViewController.m; sourceTree = ""; };
77 | 5B97711F1497912A00791728 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/WebViewController.xib; sourceTree = ""; };
78 | 5B9771221497927C00791728 /* RSTwitterEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RSTwitterEngine.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
79 | 5B9771231497927C00791728 /* RSTwitterEngine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = RSTwitterEngine.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
80 | 5BAA64D51497D8CB00ED569D /* logo_cube_57.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = logo_cube_57.png; path = ../logo_cube_57.png; sourceTree = ""; };
81 | 5BAA64D71497D8D300ED569D /* logo_cube_114.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = logo_cube_114.png; path = ../logo_cube_114.png; sourceTree = ""; };
82 | CD6AB1CB169D2F5100D2AD3F /* NSData+MKBase64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+MKBase64.h"; sourceTree = ""; };
83 | CD6AB1CC169D2F5100D2AD3F /* NSData+MKBase64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+MKBase64.m"; sourceTree = ""; };
84 | /* End PBXFileReference section */
85 |
86 | /* Begin PBXFrameworksBuildPhase section */
87 | 5B9770C0149789C200791728 /* Frameworks */ = {
88 | isa = PBXFrameworksBuildPhase;
89 | buildActionMask = 2147483647;
90 | files = (
91 | 5B412410191295DE001F575F /* ImageIO.framework in Frameworks */,
92 | 5B97711214978F6100791728 /* Security.framework in Frameworks */,
93 | 5B97711014978DBC00791728 /* SystemConfiguration.framework in Frameworks */,
94 | 5B97710E14978DB400791728 /* CFNetwork.framework in Frameworks */,
95 | 5B9770C8149789C200791728 /* UIKit.framework in Frameworks */,
96 | 5B9770CA149789C200791728 /* Foundation.framework in Frameworks */,
97 | 5B9770CC149789C200791728 /* CoreGraphics.framework in Frameworks */,
98 | );
99 | runOnlyForDeploymentPostprocessing = 0;
100 | };
101 | /* End PBXFrameworksBuildPhase section */
102 |
103 | /* Begin PBXGroup section */
104 | 5B9770B8149789C200791728 = {
105 | isa = PBXGroup;
106 | children = (
107 | 5B9770F014978CC700791728 /* MKNetworkKit */,
108 | 5B97711314978FD300791728 /* RSOAuthEngine */,
109 | 5B9770CD149789C200791728 /* TwitterDemo */,
110 | 5B9770C6149789C200791728 /* Frameworks */,
111 | 5B9770C4149789C200791728 /* Products */,
112 | );
113 | sourceTree = "";
114 | };
115 | 5B9770C4149789C200791728 /* Products */ = {
116 | isa = PBXGroup;
117 | children = (
118 | 5B9770C3149789C200791728 /* TwitterDemo.app */,
119 | );
120 | name = Products;
121 | sourceTree = "";
122 | };
123 | 5B9770C6149789C200791728 /* Frameworks */ = {
124 | isa = PBXGroup;
125 | children = (
126 | 5B41240F191295DE001F575F /* ImageIO.framework */,
127 | 5B97711114978F6100791728 /* Security.framework */,
128 | 5B97710F14978DBC00791728 /* SystemConfiguration.framework */,
129 | 5B97710D14978DB400791728 /* CFNetwork.framework */,
130 | 5B9770C7149789C200791728 /* UIKit.framework */,
131 | 5B9770C9149789C200791728 /* Foundation.framework */,
132 | 5B9770CB149789C200791728 /* CoreGraphics.framework */,
133 | );
134 | name = Frameworks;
135 | sourceTree = "";
136 | };
137 | 5B9770CD149789C200791728 /* TwitterDemo */ = {
138 | isa = PBXGroup;
139 | children = (
140 | 5B9771211497926200791728 /* Twitter */,
141 | 5B9770D6149789C200791728 /* AppDelegate.h */,
142 | 5B9770D7149789C200791728 /* AppDelegate.m */,
143 | 5B9770D9149789C200791728 /* ViewController.h */,
144 | 5B9770DA149789C200791728 /* ViewController.m */,
145 | 5B9770DC149789C200791728 /* ViewController.xib */,
146 | 5B97711B1497911300791728 /* WebViewController.h */,
147 | 5B97711C1497911300791728 /* WebViewController.m */,
148 | 5B97711E1497912A00791728 /* WebViewController.xib */,
149 | 5B9770CE149789C200791728 /* Supporting Files */,
150 | );
151 | path = TwitterDemo;
152 | sourceTree = "";
153 | };
154 | 5B9770CE149789C200791728 /* Supporting Files */ = {
155 | isa = PBXGroup;
156 | children = (
157 | 5B41240D19129582001F575F /* Default-568h@2x.png */,
158 | 5BAA64D71497D8D300ED569D /* logo_cube_114.png */,
159 | 5BAA64D51497D8CB00ED569D /* logo_cube_57.png */,
160 | 5B9770CF149789C200791728 /* TwitterDemo-Info.plist */,
161 | 5B9770D0149789C200791728 /* InfoPlist.strings */,
162 | 5B9770D3149789C200791728 /* main.m */,
163 | 5B9770D5149789C200791728 /* TwitterDemo-Prefix.pch */,
164 | );
165 | name = "Supporting Files";
166 | sourceTree = "";
167 | };
168 | 5B9770F014978CC700791728 /* MKNetworkKit */ = {
169 | isa = PBXGroup;
170 | children = (
171 | 5B9770F214978D5400791728 /* Categories */,
172 | 5B97710214978D5400791728 /* Reachability */,
173 | 5B9770FD14978D5400791728 /* MKNetworkEngine.h */,
174 | 5B9770FE14978D5400791728 /* MKNetworkEngine.m */,
175 | 5B9770FF14978D5400791728 /* MKNetworkKit.h */,
176 | 5B97710014978D5400791728 /* MKNetworkOperation.h */,
177 | 5B97710114978D5400791728 /* MKNetworkOperation.m */,
178 | );
179 | name = MKNetworkKit;
180 | path = MKNetworkKit/MKNetworkKit;
181 | sourceTree = SOURCE_ROOT;
182 | };
183 | 5B9770F214978D5400791728 /* Categories */ = {
184 | isa = PBXGroup;
185 | children = (
186 | CD6AB1CB169D2F5100D2AD3F /* NSData+MKBase64.h */,
187 | CD6AB1CC169D2F5100D2AD3F /* NSData+MKBase64.m */,
188 | 5B9770F514978D5400791728 /* NSDate+RFC1123.h */,
189 | 5B9770F614978D5400791728 /* NSDate+RFC1123.m */,
190 | 5B9770F714978D5400791728 /* NSDictionary+RequestEncoding.h */,
191 | 5B9770F814978D5400791728 /* NSDictionary+RequestEncoding.m */,
192 | 5B9770F914978D5400791728 /* NSString+MKNetworkKitAdditions.h */,
193 | 5B9770FA14978D5400791728 /* NSString+MKNetworkKitAdditions.m */,
194 | 5B9770FB14978D5400791728 /* UIAlertView+MKNetworkKitAdditions.h */,
195 | 5B9770FC14978D5400791728 /* UIAlertView+MKNetworkKitAdditions.m */,
196 | );
197 | path = Categories;
198 | sourceTree = "";
199 | };
200 | 5B97710214978D5400791728 /* Reachability */ = {
201 | isa = PBXGroup;
202 | children = (
203 | 5B97710314978D5400791728 /* Reachability.h */,
204 | 5B97710414978D5400791728 /* Reachability.m */,
205 | );
206 | path = Reachability;
207 | sourceTree = "";
208 | };
209 | 5B97711314978FD300791728 /* RSOAuthEngine */ = {
210 | isa = PBXGroup;
211 | children = (
212 | 5B97711714978FFD00791728 /* RSOAuthEngine.h */,
213 | 5B97711814978FFD00791728 /* RSOAuthEngine.m */,
214 | );
215 | path = RSOAuthEngine;
216 | sourceTree = SOURCE_ROOT;
217 | };
218 | 5B9771211497926200791728 /* Twitter */ = {
219 | isa = PBXGroup;
220 | children = (
221 | 5B9771221497927C00791728 /* RSTwitterEngine.h */,
222 | 5B9771231497927C00791728 /* RSTwitterEngine.m */,
223 | );
224 | path = Twitter;
225 | sourceTree = "";
226 | };
227 | /* End PBXGroup section */
228 |
229 | /* Begin PBXNativeTarget section */
230 | 5B9770C2149789C200791728 /* TwitterDemo */ = {
231 | isa = PBXNativeTarget;
232 | buildConfigurationList = 5B9770E1149789C200791728 /* Build configuration list for PBXNativeTarget "TwitterDemo" */;
233 | buildPhases = (
234 | 5B9770BF149789C200791728 /* Sources */,
235 | 5B9770C0149789C200791728 /* Frameworks */,
236 | 5B9770C1149789C200791728 /* Resources */,
237 | );
238 | buildRules = (
239 | );
240 | dependencies = (
241 | );
242 | name = TwitterDemo;
243 | productName = TwitterDemo;
244 | productReference = 5B9770C3149789C200791728 /* TwitterDemo.app */;
245 | productType = "com.apple.product-type.application";
246 | };
247 | /* End PBXNativeTarget section */
248 |
249 | /* Begin PBXProject section */
250 | 5B9770BA149789C200791728 /* Project object */ = {
251 | isa = PBXProject;
252 | attributes = {
253 | LastUpgradeCheck = 0510;
254 | ORGANIZATIONNAME = SharpCube;
255 | };
256 | buildConfigurationList = 5B9770BD149789C200791728 /* Build configuration list for PBXProject "TwitterDemo" */;
257 | compatibilityVersion = "Xcode 3.2";
258 | developmentRegion = English;
259 | hasScannedForEncodings = 0;
260 | knownRegions = (
261 | en,
262 | );
263 | mainGroup = 5B9770B8149789C200791728;
264 | productRefGroup = 5B9770C4149789C200791728 /* Products */;
265 | projectDirPath = "";
266 | projectRoot = "";
267 | targets = (
268 | 5B9770C2149789C200791728 /* TwitterDemo */,
269 | );
270 | };
271 | /* End PBXProject section */
272 |
273 | /* Begin PBXResourcesBuildPhase section */
274 | 5B9770C1149789C200791728 /* Resources */ = {
275 | isa = PBXResourcesBuildPhase;
276 | buildActionMask = 2147483647;
277 | files = (
278 | 5B9770D2149789C200791728 /* InfoPlist.strings in Resources */,
279 | 5B9770DE149789C200791728 /* ViewController.xib in Resources */,
280 | 5B9771201497912A00791728 /* WebViewController.xib in Resources */,
281 | 5BAA64D61497D8CB00ED569D /* logo_cube_57.png in Resources */,
282 | 5BAA64D81497D8D300ED569D /* logo_cube_114.png in Resources */,
283 | 5B41240E19129582001F575F /* Default-568h@2x.png in Resources */,
284 | );
285 | runOnlyForDeploymentPostprocessing = 0;
286 | };
287 | /* End PBXResourcesBuildPhase section */
288 |
289 | /* Begin PBXSourcesBuildPhase section */
290 | 5B9770BF149789C200791728 /* Sources */ = {
291 | isa = PBXSourcesBuildPhase;
292 | buildActionMask = 2147483647;
293 | files = (
294 | 5B9770D4149789C200791728 /* main.m in Sources */,
295 | 5B9770D8149789C200791728 /* AppDelegate.m in Sources */,
296 | 5B9770DB149789C200791728 /* ViewController.m in Sources */,
297 | 5B97710614978D5400791728 /* NSDate+RFC1123.m in Sources */,
298 | 5B97710714978D5400791728 /* NSDictionary+RequestEncoding.m in Sources */,
299 | 5B97710814978D5400791728 /* NSString+MKNetworkKitAdditions.m in Sources */,
300 | 5B97710914978D5400791728 /* UIAlertView+MKNetworkKitAdditions.m in Sources */,
301 | 5B97710A14978D5400791728 /* MKNetworkEngine.m in Sources */,
302 | 5B97710B14978D5400791728 /* MKNetworkOperation.m in Sources */,
303 | 5B97710C14978D5400791728 /* Reachability.m in Sources */,
304 | 5B97711A14978FFD00791728 /* RSOAuthEngine.m in Sources */,
305 | 5B97711D1497911300791728 /* WebViewController.m in Sources */,
306 | 5B9771241497927C00791728 /* RSTwitterEngine.m in Sources */,
307 | CD6AB1CD169D2F5100D2AD3F /* NSData+MKBase64.m in Sources */,
308 | );
309 | runOnlyForDeploymentPostprocessing = 0;
310 | };
311 | /* End PBXSourcesBuildPhase section */
312 |
313 | /* Begin PBXVariantGroup section */
314 | 5B9770D0149789C200791728 /* InfoPlist.strings */ = {
315 | isa = PBXVariantGroup;
316 | children = (
317 | 5B9770D1149789C200791728 /* en */,
318 | );
319 | name = InfoPlist.strings;
320 | sourceTree = "";
321 | };
322 | 5B9770DC149789C200791728 /* ViewController.xib */ = {
323 | isa = PBXVariantGroup;
324 | children = (
325 | 5B9770DD149789C200791728 /* en */,
326 | );
327 | name = ViewController.xib;
328 | sourceTree = "";
329 | };
330 | 5B97711E1497912A00791728 /* WebViewController.xib */ = {
331 | isa = PBXVariantGroup;
332 | children = (
333 | 5B97711F1497912A00791728 /* en */,
334 | );
335 | name = WebViewController.xib;
336 | sourceTree = "";
337 | };
338 | /* End PBXVariantGroup section */
339 |
340 | /* Begin XCBuildConfiguration section */
341 | 5B9770DF149789C200791728 /* Debug */ = {
342 | isa = XCBuildConfiguration;
343 | buildSettings = {
344 | ALWAYS_SEARCH_USER_PATHS = NO;
345 | CLANG_ENABLE_OBJC_ARC = YES;
346 | CLANG_WARN_BOOL_CONVERSION = YES;
347 | CLANG_WARN_CONSTANT_CONVERSION = YES;
348 | CLANG_WARN_EMPTY_BODY = YES;
349 | CLANG_WARN_ENUM_CONVERSION = YES;
350 | CLANG_WARN_INT_CONVERSION = YES;
351 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
352 | CODE_SIGN_IDENTITY = "iPhone Developer";
353 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
354 | COPY_PHASE_STRIP = NO;
355 | GCC_C_LANGUAGE_STANDARD = gnu99;
356 | GCC_DYNAMIC_NO_PIC = NO;
357 | GCC_OPTIMIZATION_LEVEL = 0;
358 | GCC_PREPROCESSOR_DEFINITIONS = (
359 | "DEBUG=1",
360 | "$(inherited)",
361 | );
362 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
363 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
364 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
365 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
366 | GCC_WARN_ABOUT_RETURN_TYPE = YES;
367 | GCC_WARN_UNDECLARED_SELECTOR = YES;
368 | GCC_WARN_UNINITIALIZED_AUTOS = YES;
369 | GCC_WARN_UNUSED_FUNCTION = YES;
370 | GCC_WARN_UNUSED_VARIABLE = YES;
371 | IPHONEOS_DEPLOYMENT_TARGET = 4.3;
372 | ONLY_ACTIVE_ARCH = YES;
373 | PROVISIONING_PROFILE = "";
374 | "PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
375 | SDKROOT = iphoneos;
376 | };
377 | name = Debug;
378 | };
379 | 5B9770E0149789C200791728 /* Release */ = {
380 | isa = XCBuildConfiguration;
381 | buildSettings = {
382 | ALWAYS_SEARCH_USER_PATHS = NO;
383 | CLANG_ENABLE_OBJC_ARC = YES;
384 | CLANG_WARN_BOOL_CONVERSION = YES;
385 | CLANG_WARN_CONSTANT_CONVERSION = YES;
386 | CLANG_WARN_EMPTY_BODY = YES;
387 | CLANG_WARN_ENUM_CONVERSION = YES;
388 | CLANG_WARN_INT_CONVERSION = YES;
389 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
390 | CODE_SIGN_IDENTITY = "iPhone Developer";
391 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
392 | COPY_PHASE_STRIP = YES;
393 | GCC_C_LANGUAGE_STANDARD = gnu99;
394 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
395 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
396 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES;
397 | GCC_WARN_ABOUT_RETURN_TYPE = YES;
398 | GCC_WARN_UNDECLARED_SELECTOR = YES;
399 | GCC_WARN_UNINITIALIZED_AUTOS = YES;
400 | GCC_WARN_UNUSED_FUNCTION = YES;
401 | GCC_WARN_UNUSED_VARIABLE = YES;
402 | IPHONEOS_DEPLOYMENT_TARGET = 4.3;
403 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1";
404 | PROVISIONING_PROFILE = "";
405 | "PROVISIONING_PROFILE[sdk=iphoneos*]" = "";
406 | SDKROOT = iphoneos;
407 | VALIDATE_PRODUCT = YES;
408 | };
409 | name = Release;
410 | };
411 | 5B9770E2149789C200791728 /* Debug */ = {
412 | isa = XCBuildConfiguration;
413 | buildSettings = {
414 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
415 | GCC_PREFIX_HEADER = "TwitterDemo/TwitterDemo-Prefix.pch";
416 | INFOPLIST_FILE = "TwitterDemo/TwitterDemo-Info.plist";
417 | IPHONEOS_DEPLOYMENT_TARGET = 5.0;
418 | PRODUCT_NAME = "$(TARGET_NAME)";
419 | WRAPPER_EXTENSION = app;
420 | };
421 | name = Debug;
422 | };
423 | 5B9770E3149789C200791728 /* Release */ = {
424 | isa = XCBuildConfiguration;
425 | buildSettings = {
426 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
427 | GCC_PREFIX_HEADER = "TwitterDemo/TwitterDemo-Prefix.pch";
428 | INFOPLIST_FILE = "TwitterDemo/TwitterDemo-Info.plist";
429 | IPHONEOS_DEPLOYMENT_TARGET = 5.0;
430 | PRODUCT_NAME = "$(TARGET_NAME)";
431 | WRAPPER_EXTENSION = app;
432 | };
433 | name = Release;
434 | };
435 | /* End XCBuildConfiguration section */
436 |
437 | /* Begin XCConfigurationList section */
438 | 5B9770BD149789C200791728 /* Build configuration list for PBXProject "TwitterDemo" */ = {
439 | isa = XCConfigurationList;
440 | buildConfigurations = (
441 | 5B9770DF149789C200791728 /* Debug */,
442 | 5B9770E0149789C200791728 /* Release */,
443 | );
444 | defaultConfigurationIsVisible = 0;
445 | defaultConfigurationName = Release;
446 | };
447 | 5B9770E1149789C200791728 /* Build configuration list for PBXNativeTarget "TwitterDemo" */ = {
448 | isa = XCConfigurationList;
449 | buildConfigurations = (
450 | 5B9770E2149789C200791728 /* Debug */,
451 | 5B9770E3149789C200791728 /* Release */,
452 | );
453 | defaultConfigurationIsVisible = 0;
454 | defaultConfigurationName = Release;
455 | };
456 | /* End XCConfigurationList section */
457 | };
458 | rootObject = 5B9770BA149789C200791728 /* Project object */;
459 | }
460 |
--------------------------------------------------------------------------------
/TwitterDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/TwitterDemo/AppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.h
3 | // TwitterDemo
4 | //
5 | // Created by Rodrigo Sieiro on 12/13/11.
6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 |
26 | #import
27 |
28 | @interface AppDelegate : UIResponder
29 |
30 | @property (strong, nonatomic) UIWindow *window;
31 | @property (strong, nonatomic) UINavigationController *viewController;
32 |
33 | @end
34 |
--------------------------------------------------------------------------------
/TwitterDemo/AppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.m
3 | // TwitterDemo
4 | //
5 | // Created by Rodrigo Sieiro on 12/13/11.
6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 |
26 | #import "AppDelegate.h"
27 | #import "ViewController.h"
28 |
29 | @implementation AppDelegate
30 |
31 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
32 | {
33 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
34 |
35 | ViewController *vc = [ViewController new];
36 | self.viewController = [[UINavigationController alloc] initWithRootViewController:vc];
37 | self.window.rootViewController = self.viewController;
38 | [self.window makeKeyAndVisible];
39 | return YES;
40 | }
41 |
42 | - (void)applicationWillResignActive:(UIApplication *)application
43 | {
44 | /*
45 | Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
46 | Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
47 | */
48 | }
49 |
50 | - (void)applicationDidEnterBackground:(UIApplication *)application
51 | {
52 | /*
53 | Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
54 | If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
55 | */
56 | }
57 |
58 | - (void)applicationWillEnterForeground:(UIApplication *)application
59 | {
60 | /*
61 | Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
62 | */
63 | }
64 |
65 | - (void)applicationDidBecomeActive:(UIApplication *)application
66 | {
67 | /*
68 | Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
69 | */
70 | }
71 |
72 | - (void)applicationWillTerminate:(UIApplication *)application
73 | {
74 | /*
75 | Called when the application is about to terminate.
76 | Save data if appropriate.
77 | See also applicationDidEnterBackground:.
78 | */
79 | }
80 |
81 | @end
82 |
--------------------------------------------------------------------------------
/TwitterDemo/Twitter/RSTwitterEngine.h:
--------------------------------------------------------------------------------
1 | //
2 | // RSTwitterEngine.h
3 | // RSOAuthEngine
4 | //
5 | // Created by Rodrigo Sieiro on 12/8/11.
6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 |
26 | #import "RSOAuthEngine.h"
27 |
28 | @protocol RSTwitterEngineDelegate;
29 |
30 | typedef void (^RSTwitterEngineCompletionBlock)(NSError *error);
31 |
32 | @interface RSTwitterEngine : RSOAuthEngine
33 | {
34 | RSTwitterEngineCompletionBlock _oAuthCompletionBlock;
35 | NSString *_screenName;
36 | }
37 |
38 | @property (assign) id delegate;
39 | @property (readonly) NSString *screenName;
40 |
41 | - (id)initWithDelegate:(id )delegate;
42 | - (void)authenticateWithCompletionBlock:(RSTwitterEngineCompletionBlock)completionBlock;
43 | - (void)resumeAuthenticationFlowWithURL:(NSURL *)url;
44 | - (void)cancelAuthentication;
45 | - (void)forgetStoredToken;
46 | - (void)sendTweet:(NSString *)tweet withCompletionBlock:(RSTwitterEngineCompletionBlock)completionBlock;
47 |
48 | @end
49 |
50 | @protocol RSTwitterEngineDelegate
51 |
52 | - (void)twitterEngine:(RSTwitterEngine *)engine needsToOpenURL:(NSURL *)url;
53 | - (void)twitterEngine:(RSTwitterEngine *)engine statusUpdate:(NSString *)message;
54 |
55 | @end
--------------------------------------------------------------------------------
/TwitterDemo/Twitter/RSTwitterEngine.m:
--------------------------------------------------------------------------------
1 | //
2 | // RSTwitterEngine.m
3 | // RSOAuthEngine
4 | //
5 | // Created by Rodrigo Sieiro on 12/8/11.
6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 |
26 | #import "RSTwitterEngine.h"
27 |
28 | // Never share this information
29 | #error Put your Consumer Key and Secret here, then remove this error
30 | #define TW_CONSUMER_KEY @""
31 | #define TW_CONSUMER_SECRET @""
32 |
33 | // This will be called after the user authorizes your app
34 | #define TW_CALLBACK_URL @"rstwitterengine://auth_token"
35 |
36 | // Default twitter hostname and paths
37 | #define TW_HOSTNAME @"api.twitter.com"
38 | #define TW_REQUEST_TOKEN @"oauth/request_token"
39 | #define TW_ACCESS_TOKEN @"oauth/access_token"
40 | #define TW_STATUS_UPDATE @"1.1/statuses/update.json"
41 |
42 | // URL to redirect the user for authentication
43 | #define TW_AUTHORIZE(__TOKEN__) [NSString stringWithFormat:@"https://api.twitter.com/oauth/authorize?oauth_token=%@", __TOKEN__]
44 |
45 | @interface RSTwitterEngine ()
46 |
47 | - (void)removeOAuthTokenFromKeychain;
48 | - (void)storeOAuthTokenInKeychain;
49 | - (void)retrieveOAuthTokenFromKeychain;
50 |
51 | @end
52 |
53 | @implementation RSTwitterEngine
54 |
55 | #pragma mark - Read-only Properties
56 |
57 | - (NSString *)screenName
58 | {
59 | return _screenName;
60 | }
61 |
62 | #pragma mark - Initialization
63 |
64 | - (id)initWithDelegate:(id )delegate
65 | {
66 | self = [super initWithHostName:TW_HOSTNAME
67 | customHeaderFields:nil
68 | signatureMethod:RSOAuthHMAC_SHA1
69 | consumerKey:TW_CONSUMER_KEY
70 | consumerSecret:TW_CONSUMER_SECRET
71 | callbackURL:TW_CALLBACK_URL];
72 |
73 | if (self) {
74 | _oAuthCompletionBlock = nil;
75 | _screenName = nil;
76 | self.delegate = delegate;
77 |
78 | // Retrieve OAuth access token (if previously stored)
79 | [self retrieveOAuthTokenFromKeychain];
80 | }
81 |
82 | return self;
83 | }
84 |
85 | #pragma mark - OAuth Access Token store/retrieve
86 |
87 | - (void)removeOAuthTokenFromKeychain
88 | {
89 | // Build the keychain query
90 | NSMutableDictionary *keychainQuery = [NSMutableDictionary dictionaryWithObjectsAndKeys:
91 | (__bridge_transfer NSString *)kSecClassGenericPassword, (__bridge_transfer NSString *)kSecClass,
92 | self.consumerKey, kSecAttrService,
93 | self.consumerKey, kSecAttrAccount,
94 | kCFBooleanTrue, kSecReturnAttributes,
95 | nil];
96 |
97 | // If there's a token stored for this user, delete it
98 | CFDictionaryRef query = (__bridge_retained CFDictionaryRef) keychainQuery;
99 | SecItemDelete(query);
100 | CFRelease(query);
101 | }
102 |
103 | - (void)storeOAuthTokenInKeychain
104 | {
105 | // Build the keychain query
106 | NSMutableDictionary *keychainQuery = [NSMutableDictionary dictionaryWithObjectsAndKeys:
107 | (__bridge_transfer NSString *)kSecClassGenericPassword, (__bridge_transfer NSString *)kSecClass,
108 | self.consumerKey, kSecAttrService,
109 | self.consumerKey, kSecAttrAccount,
110 | kCFBooleanTrue, kSecReturnAttributes,
111 | nil];
112 |
113 | CFTypeRef resData = NULL;
114 |
115 | // If there's a token stored for this user, delete it first
116 | CFDictionaryRef query = (__bridge_retained CFDictionaryRef) keychainQuery;
117 | SecItemDelete(query);
118 | CFRelease(query);
119 |
120 | // Build the token dictionary
121 | NSMutableDictionary *tokenDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:
122 | self.token, @"oauth_token",
123 | self.tokenSecret, @"oauth_token_secret",
124 | self.screenName, @"screen_name",
125 | nil];
126 |
127 | // Add the token dictionary to the query
128 | [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:tokenDictionary]
129 | forKey:(__bridge_transfer NSString *)kSecValueData];
130 |
131 | // Add the token data to the keychain
132 | // Even if we never use resData, replacing with NULL in the call throws EXC_BAD_ACCESS
133 | query = (__bridge_retained CFDictionaryRef) keychainQuery;
134 | SecItemAdd(query, (CFTypeRef *) &resData);
135 | CFRelease(query);
136 | }
137 |
138 | - (void)retrieveOAuthTokenFromKeychain
139 | {
140 | // Build the keychain query
141 | NSMutableDictionary *keychainQuery = [NSMutableDictionary dictionaryWithObjectsAndKeys:
142 | (__bridge_transfer NSString *)kSecClassGenericPassword, (__bridge_transfer NSString *)kSecClass,
143 | self.consumerKey, kSecAttrService,
144 | self.consumerKey, kSecAttrAccount,
145 | kCFBooleanTrue, kSecReturnData,
146 | kSecMatchLimitOne, kSecMatchLimit,
147 | nil];
148 |
149 | // Get the token data from the keychain
150 | CFTypeRef resData = NULL;
151 |
152 | // Get the token dictionary from the keychain
153 | CFDictionaryRef query = (__bridge_retained CFDictionaryRef) keychainQuery;
154 |
155 | if (SecItemCopyMatching(query, (CFTypeRef *) &resData) == noErr)
156 | {
157 | NSData *resultData = (__bridge_transfer NSData *)resData;
158 |
159 | if (resultData)
160 | {
161 | NSMutableDictionary *tokenDictionary = [NSKeyedUnarchiver unarchiveObjectWithData:resultData];
162 |
163 | if (tokenDictionary) {
164 | [self setAccessToken:[tokenDictionary objectForKey:@"oauth_token"]
165 | secret:[tokenDictionary objectForKey:@"oauth_token_secret"]];
166 |
167 | _screenName = [tokenDictionary objectForKey:@"screen_name"];
168 | }
169 | }
170 | }
171 |
172 | CFRelease(query);
173 | }
174 |
175 | #pragma mark - OAuth Authentication Flow
176 |
177 | - (void)authenticateWithCompletionBlock:(RSTwitterEngineCompletionBlock)completionBlock
178 | {
179 | // Store the Completion Block to call after Authenticated
180 | _oAuthCompletionBlock = [completionBlock copy];
181 |
182 | // First we reset the OAuth token, so we won't send previous tokens in the request
183 | [self resetOAuthToken];
184 |
185 | // OAuth Step 1 - Obtain a request token
186 | MKNetworkOperation *op = [self operationWithPath:TW_REQUEST_TOKEN
187 | params:nil
188 | httpMethod:@"POST"
189 | ssl:YES];
190 |
191 | [op addCompletionHandler:^(MKNetworkOperation *completedOperation) {
192 | // Fill the request token with the returned data
193 | [self fillTokenWithResponseBody:[completedOperation responseString] type:RSOAuthRequestToken];
194 |
195 | // OAuth Step 2 - Redirect user to authorization page
196 | [self.delegate twitterEngine:self statusUpdate:@"Waiting for user authorization..."];
197 | NSURL *url = [NSURL URLWithString:TW_AUTHORIZE(self.token)];
198 | [self.delegate twitterEngine:self needsToOpenURL:url];
199 | } errorHandler:^(MKNetworkOperation *completedOperation, NSError *error) {
200 | completionBlock(error);
201 | _oAuthCompletionBlock = nil;
202 | }];
203 |
204 | [self.delegate twitterEngine:self statusUpdate:@"Requesting Tokens..."];
205 | [self enqueueSignedOperation:op];
206 | }
207 |
208 | - (void)resumeAuthenticationFlowWithURL:(NSURL *)url
209 | {
210 | // Fill the request token with data returned in the callback URL
211 | [self fillTokenWithResponseBody:url.query type:RSOAuthRequestToken];
212 |
213 | // OAuth Step 3 - Exchange the request token with an access token
214 | MKNetworkOperation *op = [self operationWithPath:TW_ACCESS_TOKEN
215 | params:nil
216 | httpMethod:@"POST"
217 | ssl:YES];
218 |
219 | [op addCompletionHandler:^(MKNetworkOperation *completedOperation) {
220 | // Fill the access token with the returned data
221 | [self fillTokenWithResponseBody:[completedOperation responseString] type:RSOAuthAccessToken];
222 |
223 | // Retrieve the user's screen name
224 | _screenName = [self customValueForKey:@"screen_name"];
225 |
226 | // Store the OAuth access token
227 | [self storeOAuthTokenInKeychain];
228 |
229 | // Finished, return to previous method
230 | if (_oAuthCompletionBlock) _oAuthCompletionBlock(nil);
231 | _oAuthCompletionBlock = nil;
232 | } errorHandler:^(MKNetworkOperation *completedOperation, NSError *error) {
233 | if (_oAuthCompletionBlock) _oAuthCompletionBlock(error);
234 | _oAuthCompletionBlock = nil;
235 | }];
236 |
237 | [self.delegate twitterEngine:self statusUpdate:@"Authenticating..."];
238 | [self enqueueSignedOperation:op];
239 | }
240 |
241 | - (void)cancelAuthentication
242 | {
243 | NSDictionary *ui = [NSDictionary dictionaryWithObjectsAndKeys:@"Authentication cancelled.", NSLocalizedDescriptionKey, nil];
244 | NSError *error = [NSError errorWithDomain:@"com.sharpcube.RSTwitterEngine.ErrorDomain" code:401 userInfo:ui];
245 |
246 | if (_oAuthCompletionBlock) _oAuthCompletionBlock(error);
247 | _oAuthCompletionBlock = nil;
248 | }
249 |
250 | - (void)forgetStoredToken
251 | {
252 | [self removeOAuthTokenFromKeychain];
253 |
254 | [self resetOAuthToken];
255 | _screenName = nil;
256 | }
257 |
258 | #pragma mark - Public Methods
259 |
260 | - (void)sendTweet:(NSString *)tweet withCompletionBlock:(RSTwitterEngineCompletionBlock)completionBlock
261 | {
262 | if (!self.isAuthenticated) {
263 | [self authenticateWithCompletionBlock:^(NSError *error) {
264 | if (error) {
265 | // Authentication failed, return the error
266 | completionBlock(error);
267 | } else {
268 | // Authentication succeeded, call this method again
269 | [self sendTweet:tweet withCompletionBlock:completionBlock];
270 | }
271 | }];
272 |
273 | // This method will be called again once the authentication completes
274 | return;
275 | }
276 |
277 | // Fill the post body with the tweet
278 | NSMutableDictionary *postParams = [NSMutableDictionary dictionaryWithObjectsAndKeys:
279 | tweet, @"status",
280 | nil];
281 |
282 | // If the user marks the option "HTTPS Only" in his/her profile,
283 | // Twitter will fail all non-auth requests that use only HTTP
284 | // with a misleading "OAuth error". I guess it's a bug.
285 | MKNetworkOperation *op = [self operationWithPath:TW_STATUS_UPDATE
286 | params:postParams
287 | httpMethod:@"POST"
288 | ssl:YES];
289 |
290 | [op addCompletionHandler:^(MKNetworkOperation *completedOperation) {
291 | completionBlock(nil);
292 | } errorHandler:^(MKNetworkOperation *completedOperation, NSError *error) {
293 | completionBlock(error);
294 | }];
295 |
296 | [self.delegate twitterEngine:self statusUpdate:@"Sending tweet..."];
297 | [self enqueueSignedOperation:op];
298 | }
299 |
300 | @end
301 |
--------------------------------------------------------------------------------
/TwitterDemo/TwitterDemo-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | QuickTweet!
9 | CFBundleExecutable
10 | ${EXECUTABLE_NAME}
11 | CFBundleIconFiles
12 |
13 | logo_cube_57.png
14 | logo_cube_114.png
15 |
16 | CFBundleIcons
17 |
18 | CFBundlePrimaryIcon
19 |
20 | CFBundleIconFiles
21 |
22 | logo_cube_57.png
23 | logo_cube_114.png
24 |
25 | UIPrerenderedIcon
26 |
27 |
28 |
29 | CFBundleIdentifier
30 | com.sharpcube.${PRODUCT_NAME:rfc1034identifier}
31 | CFBundleInfoDictionaryVersion
32 | 6.0
33 | CFBundleName
34 | ${PRODUCT_NAME}
35 | CFBundlePackageType
36 | APPL
37 | CFBundleShortVersionString
38 | 1.0
39 | CFBundleSignature
40 | ????
41 | CFBundleVersion
42 | 1.0
43 | LSRequiresIPhoneOS
44 |
45 | UIRequiredDeviceCapabilities
46 |
47 | armv7
48 |
49 | UISupportedInterfaceOrientations
50 |
51 | UIInterfaceOrientationPortrait
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/TwitterDemo/TwitterDemo-Prefix.pch:
--------------------------------------------------------------------------------
1 | //
2 | // Prefix header for all source files of the 'TwitterDemo' target in the 'TwitterDemo' project
3 | //
4 |
5 | #import
6 |
7 | #ifndef __IPHONE_4_0
8 | #warning "This project uses features only available in iOS SDK 4.0 and later."
9 | #endif
10 |
11 | #ifdef __OBJC__
12 | #import
13 | #import
14 | #import "MKNetworkKit.h"
15 | #endif
16 |
--------------------------------------------------------------------------------
/TwitterDemo/ViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.h
3 | // TwitterDemo
4 | //
5 | // Created by Rodrigo Sieiro on 12/13/11.
6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 |
26 | #import
27 | #import "RSTwitterEngine.h"
28 | #import "WebViewController.h"
29 |
30 | @interface ViewController : UIViewController
31 |
32 | @property (strong, nonatomic) RSTwitterEngine *twitterEngine;
33 |
34 | @property (weak, nonatomic) IBOutlet UITextView *textView;
35 | @property (weak, nonatomic) IBOutlet UILabel *statusLabel;
36 |
37 | - (IBAction)sendTweet:(id)sender;
38 | - (void)swipedRight:(UIGestureRecognizer *)recognizer;
39 |
40 | @end
41 |
--------------------------------------------------------------------------------
/TwitterDemo/ViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.m
3 | // TwitterDemo
4 | //
5 | // Created by Rodrigo Sieiro on 12/13/11.
6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 |
26 | #import "ViewController.h"
27 |
28 | @implementation ViewController
29 |
30 | - (void)didReceiveMemoryWarning
31 | {
32 | [super didReceiveMemoryWarning];
33 | }
34 |
35 | #pragma mark - View Lifecycle
36 |
37 | - (void)viewDidLoad
38 | {
39 | [super viewDidLoad];
40 |
41 | if ([self respondsToSelector:@selector(edgesForExtendedLayout)]) {
42 | self.edgesForExtendedLayout = UIRectEdgeNone;
43 | }
44 |
45 | self.title = @"QuickTweet!";
46 |
47 | UIBarButtonItem *send = [[UIBarButtonItem alloc] initWithTitle:@"Send" style:UIBarButtonItemStyleDone target:self action:@selector(sendTweet:)];
48 | self.navigationItem.rightBarButtonItem = send;
49 |
50 | self.twitterEngine = [[RSTwitterEngine alloc] initWithDelegate:self];
51 |
52 | // A right swipe on the status label will clear the stored token
53 | UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipedRight:)];
54 | swipeRight.direction = UISwipeGestureRecognizerDirectionRight;
55 | swipeRight.numberOfTouchesRequired = 1;
56 | [self.statusLabel.superview addGestureRecognizer:swipeRight];
57 |
58 | // Check if the user is already authenticated
59 | if (self.twitterEngine.isAuthenticated) {
60 | self.statusLabel.text = [NSString stringWithFormat:@"Signed in as @%@.", self.twitterEngine.screenName];
61 | } else {
62 | self.statusLabel.text = @"Not signed in.";
63 | }
64 |
65 | [self.textView becomeFirstResponder];
66 | }
67 |
68 | - (void)viewDidUnload
69 | {
70 | [self setTextView:nil];
71 | [self setTwitterEngine:nil];
72 | [self setStatusLabel:nil];
73 |
74 | [super viewDidUnload];
75 | }
76 |
77 | - (void)viewWillAppear:(BOOL)animated
78 | {
79 | [super viewWillAppear:animated];
80 | }
81 |
82 | - (void)viewDidAppear:(BOOL)animated
83 | {
84 | [super viewDidAppear:animated];
85 | }
86 |
87 | - (void)viewWillDisappear:(BOOL)animated
88 | {
89 | [super viewWillDisappear:animated];
90 | }
91 |
92 | - (void)viewDidDisappear:(BOOL)animated
93 | {
94 | [super viewDidDisappear:animated];
95 | }
96 |
97 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
98 | {
99 | return (interfaceOrientation == UIInterfaceOrientationPortrait);
100 | }
101 |
102 | #pragma mark - RSTwitterEngine Delegate Methods
103 |
104 | - (void)twitterEngine:(RSTwitterEngine *)engine needsToOpenURL:(NSURL *)url
105 | {
106 | WebViewController *vc = [[WebViewController alloc] initWithURL:url];
107 | vc.delegate = self;
108 |
109 | [self presentModalViewController:[[UINavigationController alloc] initWithRootViewController:vc] animated:YES];
110 | }
111 |
112 | - (void)twitterEngine:(RSTwitterEngine *)engine statusUpdate:(NSString *)message
113 | {
114 | self.statusLabel.text = message;
115 | }
116 |
117 | #pragma mark - WebViewController Delegate Methods
118 |
119 | - (void)dismissWebView
120 | {
121 | [self dismissModalViewControllerAnimated:YES];
122 | if (self.twitterEngine) [self.twitterEngine cancelAuthentication];
123 | }
124 |
125 | - (void)handleURL:(NSURL *)url
126 | {
127 | [self dismissModalViewControllerAnimated:YES];
128 |
129 | if ([url.query hasPrefix:@"denied"]) {
130 | if (self.twitterEngine) [self.twitterEngine cancelAuthentication];
131 | } else {
132 | if (self.twitterEngine) [self.twitterEngine resumeAuthenticationFlowWithURL:url];
133 | }
134 | }
135 |
136 | #pragma mark - Custom Methods
137 |
138 | - (void)swipedRight:(UIGestureRecognizer *)recognizer
139 | {
140 | if (self.twitterEngine) [self.twitterEngine forgetStoredToken];
141 | self.statusLabel.text = @"Not signed in.";
142 | }
143 |
144 | - (IBAction)sendTweet:(id)sender
145 | {
146 | if (self.twitterEngine)
147 | {
148 | self.navigationItem.rightBarButtonItem.enabled = NO;
149 |
150 | [self.twitterEngine sendTweet:self.textView.text withCompletionBlock:^(NSError *error) {
151 | if (error) {
152 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
153 | message:[error localizedDescription]
154 | delegate:nil
155 | cancelButtonTitle:@"Dismiss"
156 | otherButtonTitles:nil];
157 | [alert show];
158 | } else {
159 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"QuickTweet!"
160 | message:@"Tweet posted successfully!"
161 | delegate:nil
162 | cancelButtonTitle:@"Dismiss"
163 | otherButtonTitles:nil];
164 | [alert show];
165 |
166 | self.textView.text = @"";
167 | }
168 |
169 | self.navigationItem.rightBarButtonItem.enabled = YES;
170 |
171 | if (self.twitterEngine.isAuthenticated) {
172 | self.statusLabel.text = [NSString stringWithFormat:@"Signed in as @%@.", self.twitterEngine.screenName];
173 | } else {
174 | self.statusLabel.text = @"Not signed in.";
175 | }
176 | }];
177 | }
178 | }
179 |
180 | @end
181 |
--------------------------------------------------------------------------------
/TwitterDemo/WebViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // WebViewController.h
3 | // TwitterDemo
4 | //
5 | // Created by Rodrigo Sieiro on 12/11/11.
6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 |
26 | #import
27 |
28 | @protocol WebViewControllerDelegate;
29 |
30 | @interface WebViewController : UIViewController
31 |
32 | @property (retain, nonatomic) NSURL *currentURL;
33 |
34 | @property (assign, nonatomic) id delegate;
35 | @property (unsafe_unretained, nonatomic) IBOutlet UIWebView *webView;
36 | @property (unsafe_unretained, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;
37 |
38 | - (id)initWithURL:(NSURL *)url;
39 | - (IBAction)cancelAction:(id)sender;
40 |
41 | @end
42 |
43 | @protocol WebViewControllerDelegate
44 |
45 | - (void)dismissWebView;
46 | - (void)handleURL:(NSURL *)url;
47 |
48 | @end
--------------------------------------------------------------------------------
/TwitterDemo/WebViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // WebViewController.m
3 | // TwitterDemo
4 | //
5 | // Created by Rodrigo Sieiro on 12/11/11.
6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 |
26 | #import "WebViewController.h"
27 | #import "AppDelegate.h"
28 |
29 | @implementation WebViewController
30 |
31 | #pragma mark - Initialization
32 |
33 | - (id)initWithURL:(NSURL *)url
34 | {
35 | self = [self initWithNibName:@"WebViewController" bundle:nil];
36 |
37 | if (self) {
38 | self.currentURL = url;
39 | self.delegate = nil;
40 | }
41 |
42 | return self;
43 | }
44 |
45 | #pragma mark - View Lifecycle
46 |
47 | - (void)viewDidLoad
48 | {
49 | [super viewDidLoad];
50 |
51 | self.title = @"Twitter";
52 | self.webView.scalesPageToFit = YES;
53 | [self.webView loadRequest:[NSURLRequest requestWithURL:self.currentURL]];
54 | }
55 |
56 | - (void)viewDidUnload
57 | {
58 | [self setWebView:nil];
59 | [self setActivityIndicator:nil];
60 |
61 | [super viewDidUnload];
62 | }
63 |
64 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
65 | {
66 | return (interfaceOrientation == UIInterfaceOrientationPortrait);
67 | }
68 |
69 | #pragma mark - UIWebView Delegate Methods
70 |
71 | - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
72 | {
73 | if ([request.URL.scheme isEqualToString:@"rstwitterengine"] && (self.delegate)) {
74 | if (self.activityIndicator) [self.activityIndicator stopAnimating];
75 | [self.delegate handleURL:request.URL];
76 | return NO;
77 | } else {
78 | return YES;
79 | }
80 | }
81 |
82 | - (void)webViewDidStartLoad:(UIWebView *)webView
83 | {
84 | if (self.activityIndicator) [self.activityIndicator startAnimating];
85 | }
86 |
87 | - (void)webViewDidFinishLoad:(UIWebView *)webView
88 | {
89 | if (self.activityIndicator) [self.activityIndicator stopAnimating];
90 | }
91 |
92 | - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
93 | {
94 | if (self.activityIndicator) [self.activityIndicator stopAnimating];
95 |
96 | UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
97 | message:[error localizedDescription]
98 | delegate:nil
99 | cancelButtonTitle:@"Dismiss"
100 | otherButtonTitles:nil];
101 | [alert show];
102 | }
103 |
104 | #pragma mark - Custom Methods
105 |
106 | - (IBAction)cancelAction:(id)sender
107 | {
108 | if (self.delegate) [self.delegate dismissWebView];
109 | }
110 |
111 | @end
112 |
--------------------------------------------------------------------------------
/TwitterDemo/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/TwitterDemo/en.lproj/ViewController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/TwitterDemo/en.lproj/WebViewController.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/TwitterDemo/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // TwitterDemo
4 | //
5 | // Created by Rodrigo Sieiro on 12/13/11.
6 | // Copyright (c) 2011-2020 Rodrigo Sieiro . All rights reserved.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 |
26 | #import
27 |
28 | #import "AppDelegate.h"
29 |
30 | int main(int argc, char *argv[])
31 | {
32 | @autoreleasepool {
33 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/logo_cube_114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rsieiro/RSOAuthEngine/ba43ddb52601df819b37e0e1a7257ca9d9ff815a/logo_cube_114.png
--------------------------------------------------------------------------------
/logo_cube_57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rsieiro/RSOAuthEngine/ba43ddb52601df819b37e0e1a7257ca9d9ff815a/logo_cube_57.png
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rsieiro/RSOAuthEngine/ba43ddb52601df819b37e0e1a7257ca9d9ff815a/screenshot.png
--------------------------------------------------------------------------------