源碼分享:iOS高仿喜馬拉雅FM
最新用空閑時間高仿了一下喜馬拉雅FM這款APP,目前主要完成了發現欄目中的推薦頁面。
效果演示
分析
+發現tab中有五個小分類,分別對應五個頁面,所有在“發現”的控制器中使用了UIPageViewController來控制五個子控制器。
+從Charles抓出來的接口來看,“推薦”頁面一共調用了三個接口,分別請求了推薦、熱門、直播的內容,所以在這里選擇了Reactivecocoa來實現接口的并發訪問
- (void)refreshDataSource {
@weakify(self);
RACSignal *signalRecommend = [RACSignal createSignal:^RACDisposable *(id
subscriber) {
@strongify(self);
[self requestRecommendList:^{
[subscriber sendNext:nil];
}];
return nil;
}];
RACSignal *signalHotAndGuess = [RACSignal createSignal:^RACDisposable *(id
subscriber) { @strongify(self); [self requestHotAndGuessList:^{ [subscriber sendNext:nil]; }]; return nil; }]; RACSignal *signalLiving = [RACSignal createSignal:^RACDisposable *(id
subscriber) { @strongify(self); [self requestLiving:^{ [subscriber sendNext:nil]; }]; return nil; }]; [[RACSignal combineLatest:@[signalRecommend,signalHotAndGuess,signalLiving]] subscribeNext:^(id x) { @strongify(self); [(RACSubject *)self.updateContentSignal sendNext:nil]; }]; } 文章轉自 耐心_朱迪的簡書
+在“推薦”頁面中有幾個輪播圖,仔細觀察會發現它的輪播圖一直想左轉換,所以這里的輪播圖片需要做一下特殊處理。以實現無限輪播的效果
- (void)setModel:(XMLYFindFocusImagesModel *)model {
_model = model;
[self.adverScrollView removeAllSubViews];
self.adverScrollView.contentSize = CGSizeMake(kScreenWidth * _model.list.count, 150);
//1.向scrollView中增加UIImageView的時候,需要在最后一張圖片后面將第一張圖片添加上去
for(NSInteger index = 0; index <= _model.list.count; index++) {
//2.如果是最后一張圖片,則放置第一張圖片
XMLYFindFocusImageDetailModel \*detail = index == _model.list.count ? _model.list.firstObject : [_model.list objectAtIndex:index];
UIImageView \*imageView = [[UIImageView alloc] init];
imageView.frame = CGRectMake(kScreenWidth \* index, 0, kScreenWidth, 150);
[imageView yy_setImageWithURL:[NSURL URLWithString:detail.pic] options:YYWebImageOptionSetImageWithFadeAnimation];
[self.adverScrollView addSubview:imageView];
}
}
在輪播圖滾動動畫結束后需要做一下判斷,如果當前滾動到了最后一張圖片,則立即將scrollView的偏移調整到初始位置,這樣一個無限輪播就完成了。
- (void)scrollViewDidScroll:(UIScrollView \*)scrollView {
NSInteger curPage = self.adverScrollView.contentOffset.x / kScreenWidth;
if(curPage == self.model.list.count) {
[self.adverScrollView setContentOffset:CGPointMake(0, 0) animated:NO];
}
}
在有輪播圖的地方肯定少不了定時器,如果將定時器直接放在cell中,就會因為cell的復用導致定時器出現問題,所有一般是將定時器放在控制器中。但是這樣的話也帶來一個問題,就是由于定時器的存在,如果要求定時器的生命周期和控制器相同(也就是在控制器dealloc的時候才取消定時器).這樣的控制器是無法調用dealloc的,會造成控制器雖然已經退出但是定時器依然在正常工作。所以這里專門為控制器設計了一個定時器的單例幫助類,這樣的話就可以在dealloc中去銷毀所有的定時器。
@interface XMLYFindRecommendHelper : NSObject
#pragma mark - Common
//生成幫助類單例
+ (instancetype)helper;
//銷毀所有的定時器
- (void)destoryAllTimer;
#pragma mark - Live
// 開啟為直播設置的定時器
- (void)startLiveTimer;
//銷毀直播的定時器
- (void)destoryLiveTimer;
#pragma mark - Header
//開啟頭部的定時器
- (void)startHeadTimer;
//銷毀頭部的定時器
- (void)destoryHeaderTimer;
@end
在廣播頁面中,有一個根據當前時間顯示不同的問候語的小功能。比如現在是早上6點鐘,應該顯示“早安*北京”。這里就需要用到NSDateFormatter,但是NSDateFormatter的比較消耗性能,所以我專門寫了一個XMLYTimeHelper類來管理所有的時間轉換操作。在這個類中對NSDateFormatter做了緩存處理,并使用dispatch_semaphore_t保證了線程安全。
//根據字符串生成相應的NSDateFormatter,比如"yyyy-MM-dd HH:mm:ss"
static force_inline NSDateFormatter *XMLYDataCreateFormatter(NSString *string) {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
formatter.dateFormat = string;
return formatter;
}
//用戶直接調用此方法,傳入"yyyy-MM-dd HH:mm:ss"這樣的字符串生成NSDateFormatter
static force_inline NSDateFormatter *XMLYDateFormatter(NSString *string) {
//1.檢查輸入的合法性
if(!string || ![string isKindOfClass:[NSString class]] || string.length == 0) return nil;
//2.初始化單例參數
static CFMutableDictionaryRef cache;
static dispatch_semaphore_t lock;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
lock = dispatch_semaphore_create(1);
});
//3.加鎖
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
//4.查詢當前字符串是否已經存在相應的NSDateformatter
NSDateFormatter *formatter = CFDictionaryGetValue(cache, (__bridge const void *)(string));
//5.解鎖
dispatch_semaphore_signal(lock);
//6.如果緩存中沒有,則需要重新生成
if(!formatter) {
formatter = XMLYDataCreateFormatter(string);
//7.重新生成成功,存入緩存
if(formatter) {
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
CFDictionarySetValue(cache, (__bridge const void *)(string), (__bridge const void *)(formatter));
dispatch_semaphore_signal(lock);
}
}
return formatter;
}