I understand your struggle intimately. What a pain it was back in the day. Iâll give it a try - I hope I donât mess up the code formatting. My code is in Objective-C. Hopefully you will understand that I do not have time to translate into Swift for you. This should give you an idea of how to grab the individual tweets from the user.
AS you may be able to see, I used good old fashioned SLRequest a lot.
I have a ViewController called TweetsListViewController with an embedded UITableView as opposed to a TableViewController. I wanted a nice profile header at the top.
In viewDidLoad I call [self loadTwitterData];
- (void)loadTwitterData {
self.startRefreshDate = [NSDate date];
[self getTwitterProfile];
[self refreshTwitterFeed];
[self getTwitterUserID];
}
The TwitterProfileView has the appâs twitter account images, follower count, following count and a follow/unfollow button above the tableview of tweets. I need the TwitterUserID to see if the user is following the app to present the correct button and the correct follow button action - follow/unfollow. Itâs kind of miss mash - Iâll clean it up later
startRefreshDate is for refreshControl using ISRefreshControl library
- (void)refreshTwitterFeed {
// check connection
if (![self checkTwitterService]) return;
__weak typeof(self)weakSelf = self;
[self.twitterAdapter refreshTwitterFeedWithCompletion:^(NSArray* jsonResponse) {
[weakSelf twitterFeedRefreshed:jsonResponse];
}];
}
- (void)twitterFeedRefreshed:(NSArray *)jsonResponse {
self.tweetsArray = jsonResponse;
[self.tweetsTableView reloadData];
NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:_startRefreshDate];
NSTimeInterval minimumInterval = 3;
if (interval > minimumInterval) {
[self.refreshControl endRefreshing];
} else {
[self.refreshControl performSelector:@selector(endRefreshing) withObject:nil afterDelay:(minimumInterval - interval)];
}
}
- (void)getTwitterProfile {
// check connection
if (![self checkTwitterService]) return;
__weak typeof(self)weakSelf = self;
[self.twitterAdapter getTwitterProfileWithCompletion:^(NSDictionary* jsonResponse) {
[weakSelf twitterProfileReceived:jsonResponse];
}];
}
-(void)twitterProfileReceived:(NSDictionary*)jsonResponse{
self.twitterAdapter.profile = [[TwitterProfile alloc] initWithJSON:jsonResponse];
[_profileView.usernameLabel setText:self.twitterAdapter.profile.name];
[_profileView.followerCountLabel setText:[self.twitterAdapter.profile.followerCount stringValue]];
[_profileView.followingCountLabel setText:[self.twitterAdapter.profile.followingCount stringValue]];
[_profileView.screenNameLabel setText:[NSString stringWithFormat:@"@%@", self.twitterAdapter.profile.screenName]];
if (self.imagesDictionary[kTwitterProfileImageKey]) {
_profileView.profilePictureImageView.image = self.imagesDictionary[kTwitterProfileImageKey];
} else {
[self getImageFromUrl:self.twitterAdapter.profile.profileImageUrl asynchronouslyForImageView:_profileView.profilePictureImageView andKey:kTwitterProfileImageKey];
[self getImageFromUrl:self.twitterAdapter.profile.profileBannerUrl asynchronouslyForImageView:_profileView.bannerImageView andKey:kTwitterBannerImageKey];
}
}
My TwitterAdapter is an NSObject Helper to consolidate Twitter calls some. TwitterProfile is just a NSObject data model
Header file:
#import <Foundation/Foundation.h>
@import Accounts;
@import Social;
#define AccountTwitterAccessGranted @"TwitterAccessGranted"
#define AccountTwitterSelectedIdentifier @"TwitterAccountSelectedIdentifier"
typedef enum {
TwitterRequestTypeFollow = 0,
TwitterRequestTypeUnFollow = 1,
TwitterRequestTypeFollowersList = 2,
TwitterRequestTypeTwitterUserID = 3
} TwitterRequestType;
@class TwitterProfile;
@interface TwitterAdapter : NSObject
@property (nonatomic, strong) ACAccount* account;
@property (nonatomic, strong) NSString *screenName;
@property (nonatomic, strong) NSString *userIdString;
@property (nonatomic, strong) TwitterProfile *profile;
- (void)accessTwitterAccountWithAccountStore:(ACAccountStore*)accountStore;
- (void)refreshTwitterFeedWithCompletion:(void (^)(NSArray* jsonResponse))completion;
- (void)getTwitterProfileWithCompletion:(void (^)(NSDictionary* jsonResponse))completion;
-(void)performTwitterRequest:(TwitterRequestType)twitterRequestType
withParameters:(NSDictionary *)params
andCompletion:(void (^)(NSDictionary *resultsDictionary))completion;
Implementation File:
#import "TwitterAdapter.h"
#import "AppDelegate.h"
#import "TwitterProfile.h"
#import <TwitterKit/TwitterKit.h>
#define API_TWITTER_FOLLOW @"https://api.twitter.com/1.1/friendships/create.json"
#define API_TWITTER_UNFOLLOW @"https://api.twitter.com/1.1/friendships/destroy.json"
#define API_TWITTER_FOLLOWERS_LIST @"https://api.twitter.com/1.1/followers/ids.json"
#define API_TWITTER_USER_ID @"https://api.twitter.com/1.1/users/show.json"
@interface TwitterAdapter ()
@property (strong, nonatomic) ACAccountStore *accountStore;
-(NSURL *)urlForTwitterRequest:(TwitterRequestType)twitterRequestType;
-(NSDictionary *)parametersForTwitterRequest:(TwitterRequestType)twitterRequestType;
-(void)startNetworkSpinner;
-(void)stopNetworkSpinner;
@end
@implementation TwitterAdapter
#pragma mark - Access AccountStore
-(void)accessTwitterAccountWithAccountStore:(ACAccountStore*)store{
[self startNetworkSpinner];
self.accountStore = store;
ACAccountType *twitterAccountType = [self.accountStore
accountTypeWithAccountTypeIdentifier:
ACAccountTypeIdentifierTwitter];
__weak typeof(self)weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.accountStore
requestAccessToAccountsWithType:twitterAccountType
options:nil completion:^(BOOL granted, NSError *error) {
if (granted) {
NSArray *twitterAccounts = [weakSelf.accountStore accountsWithAccountType:twitterAccountType];
NSString *twitterAccountIdentifier = [[NSUserDefaults standardUserDefaults] objectForKey:AccountTwitterSelectedIdentifier];
weakSelf.account = [weakSelf.accountStore accountWithIdentifier:twitterAccountIdentifier];
if (weakSelf.account) {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AccountTwitterAccessGranted
object:nil];
});
} else {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:AccountTwitterSelectedIdentifier];
[[NSUserDefaults standardUserDefaults] synchronize];
if (twitterAccounts.count > 1) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Select an account"
message:@"Please choose one of your Twitter accounts"
delegate:self
cancelButtonTitle:@"Cancel"
otherButtonTitles:nil];
for (ACAccount *account in twitterAccounts) {
[alertView addButtonWithTitle:account.accountDescription];
}
dispatch_async(dispatch_get_main_queue(), ^{
[alertView show];
});
} else {
weakSelf.account = [twitterAccounts lastObject];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AccountTwitterAccessGranted
object:nil];
});
}
}
} else {
if (error) {
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Twitter Error"
message:@"Please make sure you have a Twitter account set up in Settings. Also grant access to this app"
delegate:nil
cancelButtonTitle:@"Dismiss"
otherButtonTitles:nil];
[alertView show];
});
} else {
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Twitter Error"
message:@"We can't access Twitter, please add an account in the Settings app"
delegate:nil
cancelButtonTitle:@"Dimiss"
otherButtonTitles:nil];
[alertView show];
});
}
}
[weakSelf stopNetworkSpinner];
}];
});
}
#pragma mark - Twitter Feed
- (void)refreshTwitterFeedWithCompletion:(void (^)(NSArray* jsonResponse))completion {
if(!self.account){
ACAccountStore* store = [AppDelegate sharedInstance].accountStore;
[self accessTwitterAccountWithAccountStore:store];
return;
}
__weak typeof(self)weakSelf = self;
[self startNetworkSpinner];
NSURL* url = [NSURL URLWithString:@"https://api.twitter.com/1.1/statuses/user_timeline.json"];
NSDictionary* params = @{@"count" : @"50", @"screen_name" : self.screenName};
SLRequest *request = [SLRequest requestForServiceType:SLServiceTypeTwitter
requestMethod:SLRequestMethodGET
URL:url
parameters:params];
request.account = self.account;
[request performRequestWithHandler:^(NSData *responseData,
NSHTTPURLResponse *urlResponse, NSError *error) {
if (error) {
NSString* errorMessage = [NSString stringWithFormat:@"There was an error reading Twitter feed. %@",
[error localizedDescription]];
[[AppDelegate sharedInstance] showError:errorMessage];
} else {
NSError *jsonError;
NSArray *responseJSON = [NSJSONSerialization JSONObjectWithData:responseData
options:NSJSONReadingAllowFragments
error:&jsonError];
NSMutableArray *tweets = nil;
if (responseJSON.count > 0) {
tweets = [[NSMutableArray alloc] init];
for (NSDictionary *jsonDict in responseJSON) {
TWTRTweet *tweet = [[TWTRTweet alloc] initWithJSONDictionary:jsonDict];
[tweets addObject:tweet];
}
}
if (jsonError) {
NSString* errorMessage = [NSString stringWithFormat:@"There was an error reading your Twitter feed. %@",
[jsonError localizedDescription]];
[[AppDelegate sharedInstance] showError:errorMessage];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
if(completion){
completion(tweets);
}
});
}
}
[weakSelf stopNetworkSpinner];
}];
}
#pragma mark - Twitter Profile
- (void)getTwitterProfileWithCompletion:(void (^)(NSDictionary* jsonResponse))completion {
if(!self.account){
ACAccountStore* store = [AppDelegate sharedInstance].accountStore;
[self accessTwitterAccountWithAccountStore:store];
return;
}
__weak typeof(self)weakSelf = self;
[self startNetworkSpinner];
NSURL* url = [NSURL URLWithString:@"https://api.twitter.com/1.1/users/show.json"];
NSDictionary* params = @{@"screen_name" : self.screenName};
SLRequest *request = [SLRequest requestForServiceType:SLServiceTypeTwitter
requestMethod:SLRequestMethodGET
URL:url parameters:params];
request.account = self.account;
[request performRequestWithHandler:^(NSData *responseData,
NSHTTPURLResponse *urlResponse, NSError *error) {
if (error) {
NSString* errorMessage = [NSString stringWithFormat:@"There was an error reading Twitter feed. %@",
[error localizedDescription]];
[[AppDelegate sharedInstance] showError:errorMessage];
} else {
NSError *jsonError;
NSDictionary *responseJSON = [NSJSONSerialization JSONObjectWithData:responseData
options:NSJSONReadingMutableLeaves
error:&jsonError];
if (jsonError) {
NSString* errorMessage = [NSString stringWithFormat:@"There was an error reading Twitter feed. %@",
[jsonError localizedDescription]];
[[AppDelegate sharedInstance] showError:errorMessage];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
if(completion){
completion(responseJSON);
}
});
}
}
[weakSelf stopNetworkSpinner];
}];
}
#pragma mark - AlertView for Acounts
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex != alertView.cancelButtonIndex) {
ACAccountType *twitterAccountType = [self.accountStore accountTypeWithAccountTypeIdentifier:
ACAccountTypeIdentifierTwitter];
NSArray *twitterAccounts = [self.accountStore accountsWithAccountType:twitterAccountType];
self.account = twitterAccounts[(buttonIndex - 1)];
[[NSUserDefaults standardUserDefaults] setObject:self.account.identifier
forKey:AccountTwitterSelectedIdentifier];
[[NSUserDefaults standardUserDefaults] synchronize];
[[NSNotificationCenter defaultCenter] postNotificationName:AccountTwitterAccessGranted object:nil];
}
}
#pragma mark - Twitter Request
- (void)performTwitterRequest:(TwitterRequestType)twitterRequestType withParameters:(NSDictionary *)params andCompletion:(void (^)(NSDictionary *))completion {
if(!self.account){
ACAccountStore *store = [AppDelegate sharedInstance].accountStore;
[self accessTwitterAccountWithAccountStore:store];
return;
}
__weak typeof(self)weakSelf = self;
[self startNetworkSpinner];
// set variables
NSURL *requestUrl = [self urlForTwitterRequest:twitterRequestType];
if (params == nil)
params = [self parametersForTwitterRequest:twitterRequestType];
// set request method
SLRequestMethod requestMethod;
if (twitterRequestType == TwitterRequestTypeFollowersList || twitterRequestType == TwitterRequestTypeTwitterUserID) {
requestMethod = SLRequestMethodGET;
} else {
requestMethod = SLRequestMethodPOST;
}
// create request
SLRequest *request = [SLRequest requestForServiceType:SLServiceTypeTwitter
requestMethod:requestMethod
URL:requestUrl
parameters:params];
request.account = self.account;
// perform request
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
if (error) {
NSString *errorMessage = @"There was an error with your Twitter request.";
[[AppDelegate sharedInstance] showError:errorMessage];
} else {
NSError *jsonError = nil;
NSDictionary *responseJSONDictionary = nil;
NSString *errorMessage = nil;
if (twitterRequestType == TwitterRequestTypeFollowersList || twitterRequestType == TwitterRequestTypeTwitterUserID) {
NSArray *responseArray = [NSJSONSerialization JSONObjectWithData:responseData
options:NSJSONReadingAllowFragments
error:&jsonError];
if (twitterRequestType == TwitterRequestTypeTwitterUserID) {
responseJSONDictionary = @{@"profile" : responseArray, @"twitterRequestType" : @(twitterRequestType)};
} else {
responseJSONDictionary = @{@"statuses" : responseArray, @"twitterRequestType" : @(twitterRequestType)};
}
if (jsonError) {
errorMessage = @"There was an unknown error with Twitter.";
[[AppDelegate sharedInstance] showError:errorMessage];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
if (completion)
completion(responseJSONDictionary);
});
}
} else {
if ([urlResponse statusCode] == 200) {
NSLog(@"--->TWITTER REQUEST SUCCESS: %@", [urlResponse description]);
} else {
NSLog(@"--->TWITTER REQUEST ERROR, HTTP response: %i", [urlResponse statusCode]);
}
NSNumber *statusCode = @([urlResponse statusCode]);
NSDictionary *resultsDictionary = @{@"statusCode" : statusCode, @"twitterRequestType" : @(twitterRequestType)};
// NSLog(@"-->status: %@", resultsDictionary);
dispatch_async(dispatch_get_main_queue(), ^{
if (completion)
completion(resultsDictionary);
});
}
}
[weakSelf stopNetworkSpinner];
}];
}
#pragma mark - Helper Methods
- (NSURL *)urlForTwitterRequest:(TwitterRequestType)twitterRequestType {
NSURL *url = nil;
switch (twitterRequestType) {
case TwitterRequestTypeFollow:
url = [NSURL URLWithString:API_TWITTER_FOLLOW];
break;
case TwitterRequestTypeUnFollow:
url = [NSURL URLWithString:API_TWITTER_UNFOLLOW];
break;
case TwitterRequestTypeFollowersList:
url = [NSURL URLWithString:API_TWITTER_FOLLOWERS_LIST];
break;
case TwitterRequestTypeTwitterUserID:
url = [NSURL URLWithString:API_TWITTER_USER_ID];
break;
}
return url;
}
- (NSDictionary *)parametersForTwitterRequest:(TwitterRequestType)twitterRequestType {
NSDictionary *params = nil;
switch (twitterRequestType) {
case TwitterRequestTypeFollow:
params = @{@"screen_name" : self.screenName, @"follow" : @"true"};
break;
case TwitterRequestTypeUnFollow:
params = @{@"screen_name" : self.screenName};
break;
case TwitterRequestTypeFollowersList:
params = @{@"screen_name" : self.screenName, @"skip_status" : @"1", @"count" : @"5000", @"include_user_entities" : @"0", @"stringify_ids" : @"1"};
break;
case TwitterRequestTypeTwitterUserID:
params = @{@"screen_name" : self.account.username};
break;
}
return params;
}
#pragma mark - Network Spinners
- (void)startNetworkSpinner {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
}
- (void)stopNetworkSpinner {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}
#pragma mark - Getters
- (NSString *)screenName {
if (!_screenName)
self.screenName = kAppTwitterScreenName;
return _screenName;
}
@end
Back to TweetsListViewController
- (void)getTwitterUserID {
// check connection
if (![self checkTwitterService]) return;
__weak typeof(self)weakSelf = self;
[self.twitterAdapter performTwitterRequest:TwitterRequestTypeTwitterUserID
withParameters:nil
andCompletion:
^(NSDictionary *resultsDictionary) {
weakSelf.twitterAdapter.userIdString = resultsDictionary[@"profile"][@"id_str"];
[weakSelf getFollowersList];
}];
}
- (void)getFollowersList {
// check connection
if (![self checkTwitterService]) return;
__weak typeof(self)weakSelf = self;
[self.twitterAdapter performTwitterRequest:TwitterRequestTypeFollowersList
withParameters:nil
andCompletion:
^(NSDictionary *resultsDictionary) {
NSArray *usersArray = resultsDictionary[@"statuses"][@"ids"];
BOOL idMatch = NO;
for (NSString *followerID in usersArray) {
if ([followerID isEqualToString:weakSelf.twitterAdapter.userIdString]) {
NSLog(@"------>MATCH!!! => isFollowing = %d", weakSelf.twitterAdapter.profile.isFollowing);
idMatch = YES;
break;
}
}
weakSelf.twitterAdapter.profile.isFollowing = idMatch;
NSLog(@"------>isFollowing = %d", weakSelf.twitterAdapter.profile.isFollowing);
[weakSelf configureFollowButton:weakSelf.twitterAdapter.profile.isFollowing];
}];
}
- (IBAction)followMeButtonPressed:(id)sender {
if (self.twitterAdapter.profile.isFollowing) {
__weak typeof(self)weakSelf = self;
NSString *message = [NSString stringWithFormat:@"Are you sure you wish to Un-Follow @%@?", self.twitterAdapter.screenName];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Twitter"
message:message
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *action = [UIAlertAction actionWithTitle:@"Yes"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[weakSelf processFollowRequest];
}];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"No"
style:UIAlertActionStyleCancel
handler:nil];
[alert addAction:action];
[alert addAction:cancel];
[self presentViewController:alert animated:YES completion:nil];
} else {
[self processFollowRequest];
}
}
- (void)processFollowRequest {
// check connection
if (![self checkTwitterService]) return;
__weak typeof(self)weakSelf = self;
TwitterRequestType followTwitterRequest = TwitterRequestTypeFollow;
if (self.twitterAdapter.profile.isFollowing)
followTwitterRequest = TwitterRequestTypeUnFollow;
[self.twitterAdapter performTwitterRequest:followTwitterRequest
withParameters:nil
andCompletion:
^(NSDictionary *resultsDictionary) {
NSString *statusCode = [resultsDictionary[@"statusCode"] stringValue];
if ([statusCode isEqualToString:@"200"]) {
NSLog(@"--->SUCCESS FOLLOW - UNFOLLOW | STATUSCODE = %d@", statusCode);
weakSelf.twitterAdapter.profile.isFollowing = !weakSelf.twitterAdapter.profile.isFollowing;
[weakSelf getTwitterProfile];
[weakSelf configureFollowButton:weakSelf.twitterAdapter.profile.isFollowing];
} else {
NSString *failureMessage = nil;
NSString *title = @"Twitter";
if (followTwitterRequest == TwitterRequestTypeFollow)
failureMessage = [NSString stringWithFormat:@"There was an Error trying to follow %@.", weakSelf.twitterAdapter.screenName];
else if (followTwitterRequest == TwitterRequestTypeUnFollow)
failureMessage = [NSString stringWithFormat:@"There was an Error trying to un-follow %@.", weakSelf.twitterAdapter.screenName];
[weakSelf showAlertWithMessage:failureMessage andTitle:title];
}
}];
}