OC高仿iOS网易云音乐AFNetworking+SDWebImage+MJRefresh+MVC+MVVM

Posted 爱上学习啊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OC高仿iOS网易云音乐AFNetworking+SDWebImage+MJRefresh+MVC+MVVM相关的知识,希望对你有一定的参考价值。

效果

因为OC版本大部分截图和Swift版本一样,所以就不再另外截图了。

列文章目录

因为目录比较多,每次更新这里比较麻烦,所以推荐点击到主页,然后查看ios云音乐专栏。

目简介

这是一个使用OC语言(还有Swift,android版本),从0开发一个iOS平台,接近企业级的项目(我的云音乐),包含了基础内容,高级内容,项目封装,项目重构等知识;主要是使用系统功能,流行的第三方框架,第三方服务,完成接近企业级商业级项目。

目功能点

隐私协议对话框
启动界面和动态处理权限
引导界面和广告
轮播图和侧滑菜单
首页复杂列表和列表排序
音乐播放和音乐列表管理
全局音乐控制条
桌面歌词和自定义样式
全局媒体控制中心
评论和回复评论
评论富文本点击
评论提醒人和话题
朋友圈动态列表和发布
高德地图定位和路径规划
阿里云OSS上传
视频播放和控制
QQ/微信登录和分享
商城/购物车\\微信\\支付宝支付
文本和图片聊天
消息离线推送
自动和手动检查更新
内存泄漏和优化

发环境概述

2022年5月开发完成的,所以全部都是最新的,平均每3年会重新制作,现在已经是第三版了。

Xcode 13.4
iOS 15

译和运行

先安装pod,用最新Xcode打开MyCloudMusic.xcworkspace,然后运行,如果要运行到真机,先登陆自己的开发者账户,如果不是付费账户,请删除推送等付费功能,更改BundleId,然后运行。

目目录结构

├── MyCloudMusic
│   ├── AppDelegate.h
│   ├── AppDelegate.m
│   ├── Assets.xcassets #资源目录
│   ├── Base.lproj
│   ├── Cell #通用cell
│   ├── Component #每个功能模块
│   │   ├── Ad #广告相关
│   │   ├── Address #收货地址相关
│   ├── Config #配置目录,例如:网络地址配置
│   ├── Controller #通用控制器
│   ├── Extension #扩展,例如:字符串扩展
│   ├── Info.plist
│   ├── Manager #管理器,例如:音乐播放管理器
│   ├── Model  #通用模型
│   ├── MyCloudMusic.entitlements
│   ├── Network
│   ├── PrefixHeader.pch
│   ├── Repository #数据仓库,例如:网络请求封装
│   ├── Util #工具类
│   ├── Vender #通过源码方式依赖的第三方框架
│   ├── View #通用View
│   ├── ViewController.h
│   ├── ViewController.m
│   ├── main.m
│   └── zh-Hans.lproj
├── MyCloudMusic.xcodeproj
├── MyCloudMusic.xcworkspace
├── MyCloudMusicTests
│   └── MyCloudMusicTests.m
├── MyCloudMusicUITests
├── Podfile
├── Podfile.lock
├── R.h
├── R.m
└── ixueaeduTestVideo.mp4

赖框架

内容太多,只列出部分。

target 'MyCloudMusic' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for MyCloudMusic
  #腾讯开源的UI框架,提供了很多功能,例如:圆角按钮,空心按钮,TextView支持placeholder
  #https://github.com/QMUI/QMUIDemo_iOS
  #https://qmuiteam.com/ios/get-started
  pod "QMUIKit"
  
  #https://github.com/SysdataSpA/R.objc
  #作者说受R.swift的自由启发,获取自动完成的本地化字符串、资产目录图像名称和故事板对象
  pod 'R.objc'
  
  #轮播图
  #https://github.com/QuintGao/GKCycleScrollView
  pod 'GKCycleScrollView'
  
  #网络框架
  #https://github.com/AFNetworking/AFNetworking
  pod 'AFNetworking'

  
  #轮播图,多讲解一个是方便大家选择
  #https://github.com/wwmz/WMZBanner
  pod 'WMZBanner'
  
  #https://github.com/91renb/BRPickerView
  #封装的是iOS中常用的选择器组件,主要包括:日期选择器
  pod 'BRPickerView'
  
  #支付宝支付
  #https://docs.open.alipay.com/204/105295/
  pod 'AlipaySDK-iOS'
  
  #融云聊天
  #https://doc.rongcloud.cn/im/IOS/5.X/noui/import
  pod 'RongCloudIM/IMLib'
  
  pod 'JCore'

  #极光推送
  #https://docs.jiguang.cn/jpush/client/iOS/ios_guide_new/
  pod 'JPush'
  
  #极光统计
  #https://docs.jiguang.cn/janalytics/guideline/intro/
  pod 'JAnalytics'
  
  #webview和js交互框架
  #可以直接使用系统提供的api,不是说一定要用框架
  #只是用该框架,更方便
  #https://github.com/marcuswestin/WebViewjavascriptBridge
  pod 'WebViewJavascriptBridge'
  
  target 'MyCloudMusicTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'MyCloudMusicUITests' do
    # Pods for testing
  end

end

户协议对话框

使用自定义Dialog实现。

@interface TermServiceDialogController ()<QMUIModalPresentationContentViewControllerProtocol>

@end

@implementation TermServiceDialogController
- (void)initViews
    [super initViews];
    
    self.view.backgroundColor=[UIColor colorDivider];
    self.view.myWidth=MyLayoutSize.fill;
    self.view.myHeight=MyLayoutSize.wrap;
    
    //根容器
    self.rootContainer = [[MyLinearLayout alloc] initWithOrientation:MyOrientation_Vert];
    self.rootContainer.subviewSpace=0.5;
    self.rootContainer.myWidth=MyLayoutSize.fill;
    self.rootContainer.myHeight=MyLayoutSize.wrap;
    [self.view addSubview:self.rootContainer];
    
    //内容容器
    self.contentContainer = [[MyLinearLayout alloc] initWithOrientation:MyOrientation_Vert];
    self.contentContainer.subviewSpace=25;
    self.contentContainer.myWidth=MyLayoutSize.fill;
    self.contentContainer.myHeight=MyLayoutSize.wrap;
    self.contentContainer.backgroundColor = [UIColor colorBackground];
    self.contentContainer.padding=UIEdgeInsetsMake(PADDING_LARGE2, PADDING_OUTER, PADDING_LARGE2, PADDING_OUTER);
    self.contentContainer.gravity=MyGravity_Horz_Center;
    [self.rootContainer addSubview:self.contentContainer];
    
    //标题
    [self.contentContainer addSubview:self.titleView];
    
    self.textView=[UITextView new];
    self.textView.myWidth=MyLayoutSize.fill;
    
    //超出的内容,自动支持滚动
    self.textView.myHeight=230;
    self.textView.text=@"...";
    self.textView.backgroundColor = [UIColor clearColor];
    
    //禁用编辑
    self.textView.editable=NO;
    
    [self.contentContainer addSubview:self.textView];
    
    [self.contentContainer addSubview:self.primaryButton];
    
    //不同意按钮按钮
    self.disagreeButton = [ViewFactoryUtil linkButton];
    [self.disagreeButton setTitle:R.string.localizable.disagree forState: UIControlStateNormal];
    [self.disagreeButton setTitleColor:[UIColor black80] forState:UIControlStateNormal];
    [self.disagreeButton addTarget:self action:@selector(disagreeClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.disagreeButton sizeToFit];
    [self.contentContainer addSubview:self.disagreeButton];


- (void)show
    self.modalController = [QMUIModalPresentationViewController new];
    self.modalController.animationStyle = QMUIModalPresentationAnimationStyleFade;
    
    //点击外部不隐藏
    [self.modalController setModal:YES];
    
    //边距
    self.modalController.contentViewMargins=UIEdgeInsetsMake(PADDING_LARGE2, PADDING_LARGE2, PADDING_LARGE2, PADDING_LARGE2);
    
    //设置要显示的内容控件
    self.modalController.contentViewController=self;
    
    [self.modalController showWithAnimated:YES completion:nil];


- (void)hide
    [self.modalController hideWithAnimated:YES completion:nil];


#pragma mark - 创建控件
- (UILabel *)titleView
    if (!_titleView) 
        _titleView=[UILabel new];
        _titleView.myWidth=MyLayoutSize.fill;
        _titleView.myHeight=MyLayoutSize.wrap;
        _titleView.text=@"标题";
        _titleView.textAlignment=NSTextAlignmentCenter;
        _titleView.font=[UIFont boldSystemFontOfSize:TEXT_LARGE3];
        _titleView.textColor=[UIColor colorOnSurface];
    
    return _titleView;


- (QMUIButton *)primaryButton
    if (!_primaryButton) 
        _primaryButton = [ViewFactoryUtil primaryHalfFilletButton];
        [_primaryButton setTitle:R.string.localizable.agree forState:UIControlStateNormal];
    
    return _primaryButton;

@end

导界面

引导界面比较简单,就是多个图片可以左右滚动。

@interface GuideController ()<GKCycleScrollViewDataSource,GKCycleScrollViewDelegate>
@property (nonatomic, strong) GKCycleScrollView *contentScrollView;
@end

@implementation GuideController
- (void)initViews
    [super initViews];
    
    [self initLinearLayoutSafeArea];
    
    //轮播图器容器
    MyRelativeLayout *bannerContainer=[MyRelativeLayout new];
    bannerContainer.myWidth=MyLayoutSize.fill;
    bannerContainer.myHeight=MyLayoutSize.wrap;
    bannerContainer.weight=1;
    [self.container addSubview:bannerContainer];
    
    //轮播图
    _contentScrollView=[GKCycleScrollView new];
    _contentScrollView.backgroundColor = [UIColor clearColor];
    _contentScrollView.dataSource = self;
    _contentScrollView.delegate = self;
    _contentScrollView.myWidth = MyLayoutSize.fill;
    _contentScrollView.myHeight = MyLayoutSize.fill;
    
    //禁用自动滚动
    _contentScrollView.isAutoScroll=NO;
    
    //不改变透明度
    _contentScrollView.isChangeAlpha=NO;
    
    _contentScrollView.clipsToBounds = YES;
    [bannerContainer addSubview:_contentScrollView];
    
    //按钮容器
    MyLinearLayout *controlContainer=[[MyLinearLayout alloc] initWithOrientation:MyOrientation_Horz];
    controlContainer.myBottom=PADDING_LARGE2;
    controlContainer.myWidth=MyLayoutSize.fill;
    controlContainer.myHeight=MyLayoutSize.wrap;
    
    //水平拉升,左,中,右间距一样
    controlContainer.gravity = MyGravity_Horz_Among;
    [self.container addSubview:controlContainer];
    
    //登录注册按钮
    QMUIButton *primaryButton = [ViewFactoryUtil primaryButton];
    [primaryButton setTitle:R.string.localizable.loginOrRegister forState:UIControlStateNormal];
    [primaryButton addTarget:self action:@selector(onPrimaryClick:) forControlEvents:UIControlEventTouchUpInside];
    primaryButton.myWidth=BUTTON_WIDTH_MEDDLE;
    [controlContainer addSubview:primaryButton];


- (void)initDatum
    [super initDatum];
    self.datum = [NSMutableArray array];
    
    [self.datum addObject:R.image.guide1];
    [self.datum addObject:R.image.guide2];
    [self.datum addObject:R.image.guide3];
    [self.datum addObject:R.image.guide4];
    [self.datum addObject:R.image.guide5];
    [_contentScrollView reloadData];


- (void)onPrimaryClick:(QMUIButton *)sender
    [AppDelegate.shared toLogin];



#pragma mark  轮播图数据源

/// 有多少个
/// @param cycleScrollView <#cycleScrollView description#>
- (NSInteger)numberOfCellsInCycleScrollView:(GKCycleScrollView *)cycleScrollView
    return self.datum.count;


/// 返回cell
/// @param cycleScrollView <#cycleScrollView description#>
/// @param index <#index description#>
- (GKCycleScrollViewCell *)cycleScrollView:(GKCycleScrollView *)cycleScrollView cellForViewAtIndex:(NSInteger)index 
    GKCycleScrollViewCell *cell = [cycleScrollView dequeueReusableCell];
    if (!cell) 
        cell = [GKCycleScrollViewCell new];
    

    UIImage *data=[self.datum objectAtIndex:index];

    cell.imageView.image = data;
    cell.imageView.contentMode = UIViewContentModeScaleAspectFit;

    return cell;

@end

广告界面

实现图片广告和视频广告,广告数据是在首页是缓存到本地,目的是在启动界面加载更快,因为真实项目中,大部分项目启动页面广告时间一共就5秒,如果太长了用户体验不好,如果是从网络请求,那么网络可能就耗时2秒左右,所以导致就美哟多少时间显示广告了。

广告

func downloadAd(_ data:Ad,_ path:URL) 
    let destination: DownloadRequest.Destination =  _, _ in
        return (path, [.removePreviousFile, .createIntermediateDirectories])
    

    AF.download(data.icon.absoluteUri(), to: destination).response  response in
        if response.error == nil, let filePath = response.fileURL?.path 
            print("ad downloaded success \\(filePath)")
        
    

广告

-(void)showVideoAd:(NSURL *)data
    //播放应用内嵌入视频,放根目录中
    //同样其他的文件,也可以通过这种方式读取
	//data = [NSBundle.mainBundle URLForResource:@"ixueaeduTestVideo" withExtension:@".mp4"];

    _player = [AVPlayer playerWithURL:data];

    //静音
    _player.muted = YES;

    /// 添加进度监听
    __weak typeof(self) weakSelf = self;
    [_player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) 
        //当前时间,秒
        Float64 current=CMTimeGetSeconds(weakSelf.player.currentItem.currentTime);

        //总时间
        CGFloat duration =  CMTimeGetSeconds(weakSelf.player.currentItem.duration);

        if (current==duration) 
            //视频播放结束
            [weakSelf next];
         else 
            [weakSelf.skipView setTitle:[R.string.localizable skipAdCount:(NSInteger)(duration-current)] forState:UIControlStateNormal];
            weakSelf.skipView.myWidth=MyLayoutSize.wrap;
            [weakSelf.skipView setNeedsLayout];

        
    ];

    [self.player play];

    //显示图像
    self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];

    //从中心等比缩放,完全显示控件
    self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;

    [self.view.layer insertSublayer:self.playerLayer atIndex:0];

显示图片就是显示本地图片了,没什么难点,就不贴代码了。

首页/歌单详情/黑胶唱片界面

首页没有顶部是轮播图,然后是可以左右的菜单,接下来是热门歌单,推荐单曲,最后是首页排序模块;整体上使用RecycerView实现,轮播图:

//轮播图
BannerCell *cell = [tableView dequeueReusableCellWithIdentifier:BannerCellName forIndexPath:indexPath];

//绑定数据
[cell bind:data];

return cell;

详情

顶部是歌单信息,通过Cell实现,底部是列表,显示歌单内容的音乐,点击音乐进入黑胶唱片播放界面。

@implementation SheetDetailController

- (void)initViews
    [super initViews];
    //添加背景图片控件
    _backgroundImageView = [UIImageView new];

    //默认隐藏
    _backgroundImageView.clipsToBounds = YES;
    _backgroundImageView.alpha = 0;
    _backgroundImageView.contentMode = UIViewContentModeScaleAspectFill;
    [self.view addSubview:self.backgroundImageView];

    ...
    
    //注册歌单信息
    [self.tableView registerClass:[SheetInfoCell class] forCellReuseIdentifier:SheetInfoCellName];
    
    //注册section
    [self.tableView registerClass:[SongGroupHeaderView class] forHeaderFooterViewReuseIdentifier:SongGroupHeaderViewName];

    //注册单曲
    [self.tableView registerClass:[SongCell class] forCellReuseIdentifier:SongCellName];


- (void)initListeners
    [super initListeners];
    @weakify(self);
    
    //点击事件
    [QTSubMain(self,ClickEvent) next:^(ClickEvent *event) 
        @strongify(self);
        [self processClick:event.style];
    ];


...

-(void)loadData:(BOOL)isPlaceholder
    [[DefaultRepository shared] sheetDetailWithId:_id success:^(BaseResponse * _Nonnull baseResponse, id  _Nonnull data) 
        [self show:data];
    ];


-(void)show:(Sheet *)data
    self.data=data;
    
    [ImageUtil show:self.backgroundImageView uri:data.icon];

    //使用动画显示背景图片
    [UIView animateWithDuration:0.3 animations:^
        //透明度设置为1
        self.backgroundImageView.alpha=1;
    ];
    
    [self.datum removeAllObjects];
    
    //第一组
    SongGroupData *groupData=[SongGroupData new];
    NSMutableArray *tempArray = [NSMutableArray new];
    [tempArray addObject:data];
    groupData.datum=tempArray;
    [self.datum addObject:groupData];
    
    if (data.songs) 
        //有音乐才设置

        //设置数据
        groupData=[SongGroupData new];
        NSMutableArray *tempArray = [NSMutableArray new];
        [tempArray addObjectsFromArray:data.songs];
        [tempArray addObjectsFromArray:data.songs];
        groupData.datum=tempArray;
        [self.datum addObject:groupData];
    
    
    [self.tableView reloadData];


/// 播放音乐
/// @param data <#data description#>
-(void)play:(Song *)data
    //把当前歌单所有音乐设置到播放列表
    //有些应用
    //可能会实现添加到已经播放列表功能
    [[MusicListManager shared] setDatum:self.data.songs];

    //播放当前音乐
    [[MusicListManager shared] play:data];
    
    [self startMusicPlayerController];


/// 有多少组
/// @param tableView <#tableView description#>
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    return self.datum.count;


/// 当前组有多少个
/// @param tableView <#tableView description#>
/// @param section <#sectio

以上是关于OC高仿iOS网易云音乐AFNetworking+SDWebImage+MJRefresh+MVC+MVVM的主要内容,如果未能解决你的问题,请参考以下文章

OC高仿iOS网易云音乐AFNetworking+SDWebImage+MJRefresh+MVC+MVVM

Swift高仿iOS网易云音乐Moya+RxSwift+Kingfisher+MVC+MVVM

Swift高仿iOS网易云音乐Moya+RxSwift+Kingfisher+MVC+MVVM

Swift高仿iOS网易云音乐Moya+RxSwift+Kingfisher+MVC+MVVM

高仿Android网易云音乐OkHttp+Retrofit+RxJava+Glide+MVC+MVVM

新鲜出炉高仿网易云音乐 APP