欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

ios開發(fā):一個(gè)音樂播放器的設(shè)計(jì)與實(shí)現(xiàn)案例

 更新時(shí)間:2016年11月03日 10:38:15   作者:紫憶  
本篇文章主要介紹了ios開發(fā):一個(gè)音樂播放器的設(shè)計(jì)與實(shí)現(xiàn)案例,具有一定的參考價(jià)值,有需要的小伙伴可以參考下。

這個(gè)Demo,關(guān)于歌曲播放的主要功能都實(shí)現(xiàn)了的。下一曲、上一曲,暫停,根據(jù)歌曲的播放進(jìn)度動(dòng)態(tài)滾動(dòng)歌詞,將當(dāng)前正在播放的歌詞放大顯示,拖動(dòng)進(jìn)度條,歌曲跟著變化,并且使用Time Profiler進(jìn)行了優(yōu)化,還使用XCTest對(duì)幾個(gè)主要的類進(jìn)行了單元測(cè)試。

已經(jīng)經(jīng)過真機(jī)調(diào)試,在真機(jī)上可以后臺(tái)播放音樂,并且鎖屏?xí)r,顯示一些主要的歌曲信息。

根據(jù)歌曲的播放來顯示對(duì)應(yīng)歌詞的。用UITableView來顯示歌詞,可以手動(dòng)滾動(dòng)界面查看后面或者前面的歌詞。

并且,當(dāng)拖動(dòng)進(jìn)度條,歌詞也會(huì)隨之變化,下一曲、上一曲依然是可以使用的。

代碼分析:

準(zhǔn)備階段,先是寫了一個(gè)音頻播放的單例,用這個(gè)單例來播放這個(gè)demo中的音樂文件,代碼如下:

#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
@interface ZYAudioManager : NSObject
+ (instancetype)defaultManager;
 
//播放音樂
- (AVAudioPlayer *)playingMusic:(NSString *)filename;
- (void)pauseMusic:(NSString *)filename;
- (void)stopMusic:(NSString *)filename;
 
//播放音效
- (void)playSound:(NSString *)filename;
- (void)disposeSound:(NSString *)filename;
@end
 
 
 
#import "ZYAudioManager.h"
 
@interface ZYAudioManager ()
@property (nonatomic, strong) NSMutableDictionary *musicPlayers;
@property (nonatomic, strong) NSMutableDictionary *soundIDs;
@end
 
static ZYAudioManager *_instance = nil;
 
@implementation ZYAudioManager
 
+ (void)initialize
{
  // 音頻會(huì)話
  AVAudioSession *session = [AVAudioSession sharedInstance];
   
  // 設(shè)置會(huì)話類型(播放類型、播放模式,會(huì)自動(dòng)停止其他音樂的播放)
  [session setCategory:AVAudioSessionCategoryPlayback error:nil];
   
  // 激活會(huì)話
  [session setActive:YES error:nil];
}
 
+ (instancetype)defaultManager
{
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    _instance = [[self alloc] init];
  });
  return _instance;
}
 
- (instancetype)init
{
  __block ZYAudioManager *temp = self;
   
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    if ((temp = [super init]) != nil) {
      _musicPlayers = [NSMutableDictionary dictionary];
      _soundIDs = [NSMutableDictionary dictionary];
    }
  });
  self = temp;
  return self;
}
 
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    _instance = [super allocWithZone:zone];
  });
  return _instance;
}
 
//播放音樂
- (AVAudioPlayer *)playingMusic:(NSString *)filename
{
  if (filename == nil || filename.length == 0) return nil;
   
  AVAudioPlayer *player = self.musicPlayers[filename];   //先查詢對(duì)象是否緩存了
   
  if (!player) {
    NSURL *url = [[NSBundle mainBundle] URLForResource:filename withExtension:nil];
     
    if (!url) return nil;
     
    player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
     
    if (![player prepareToPlay]) return nil;
     
    self.musicPlayers[filename] = player;      //對(duì)象是最新創(chuàng)建的,那么對(duì)它進(jìn)行一次緩存
  }
   
  if (![player isPlaying]) {         //如果沒有正在播放,那么開始播放,如果正在播放,那么不需要改變什么
    [player play];
  }
  return player;
}
 
- (void)pauseMusic:(NSString *)filename
{
  if (filename == nil || filename.length == 0) return;
   
  AVAudioPlayer *player = self.musicPlayers[filename];
   
  if ([player isPlaying]) {
    [player pause];
  }
}
- (void)stopMusic:(NSString *)filename
{
  if (filename == nil || filename.length == 0) return;
   
  AVAudioPlayer *player = self.musicPlayers[filename];
   
  [player stop];
   
  [self.musicPlayers removeObjectForKey:filename];
}
 
//播放音效
- (void)playSound:(NSString *)filename
{
  if (!filename) return;
   
  //取出對(duì)應(yīng)的音效ID
  SystemSoundID soundID = (int)[self.soundIDs[filename] unsignedLongValue];
   
  if (!soundID) {
    NSURL *url = [[NSBundle mainBundle] URLForResource:filename withExtension:nil];
    if (!url) return;
     
    AudioServicesCreateSystemSoundID((__bridge CFURLRef)(url), &soundID);
     
    self.soundIDs[filename] = @(soundID);
  }
   
  // 播放
  AudioServicesPlaySystemSound(soundID);
}
 
//摧毀音效
- (void)disposeSound:(NSString *)filename
{
  if (!filename) return;
   
   
  SystemSoundID soundID = (int)[self.soundIDs[filename] unsignedLongValue];
   
  if (soundID) {
    AudioServicesDisposeSystemSoundID(soundID);
     
    [self.soundIDs removeObjectForKey:filename];  //音效被摧毀,那么對(duì)應(yīng)的對(duì)象應(yīng)該從緩存中移除
  }
}
@end

 就是一個(gè)單例的設(shè)計(jì),并沒有多大難度。我是用了一個(gè)字典來裝播放過的歌曲了,這樣如果是暫停了,然后再開始播放,就直接在緩存中加載即可。但是如果不注意,在 stopMusic:(NSString *)fileName  這個(gè)方法里面,不從字典中移除掉已經(jīng)停止播放的歌曲,那么你下再播放這首歌的時(shí)候,就會(huì)在原先播放的進(jìn)度上繼續(xù)播放。在編碼過程中,我就遇到了這個(gè)Bug,然后發(fā)現(xiàn),在切換歌曲(上一曲、下一曲)的時(shí)候,我調(diào)用的是stopMusic方法,但由于我沒有從字典中將它移除,而導(dǎo)致它總是從上一次的進(jìn)度開始播放,而不是從頭開始播放。

如果在真機(jī)上想要后臺(tái)播放歌曲,除了在appDelegate以及plist里面做相應(yīng)操作之外,還得將播放模式設(shè)置為:AVAudioSessionCategoryPlayback。特別需要注意這里,我在模擬器上調(diào)試的時(shí)候,沒有設(shè)置這種模式也是可以進(jìn)行后臺(tái)播放的,但是在真機(jī)上卻不行了。后來在StackOverFlow上找到了對(duì)應(yīng)的答案,需要設(shè)置播放模式。

這個(gè)單例類,在整個(gè)demo中是至關(guān)重要的,要保證它是沒有錯(cuò)誤的,所以我寫了這個(gè)類的XCTest進(jìn)行單元測(cè)試,代碼如下:

#import <XCTest/XCTest.h>
#import "ZYAudioManager.h"
#import <AVFoundation/AVFoundation.h>
 
@interface ZYAudioManagerTests : XCTestCase
@property (nonatomic, strong) AVAudioPlayer *player;
@end
static NSString *_fileName = @"10405520.mp3";
@implementation ZYAudioManagerTests
 
- (void)setUp {
  [super setUp];
  // Put setup code here. This method is called before the invocation of each test method in the class.
}
 
- (void)tearDown {
  // Put teardown code here. This method is called after the invocation of each test method in the class.
  [super tearDown];
}
 
- (void)testExample {
  // This is an example of a functional test case.
  // Use XCTAssert and related functions to verify your tests produce the correct results.
}
 
/**
 * 測(cè)試是否為單例,要在并發(fā)條件下測(cè)試
 */
- (void)testAudioManagerSingle
{
  NSMutableArray *managers = [NSMutableArray array];
   
  dispatch_group_t group = dispatch_group_create();
   
  dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    ZYAudioManager *tempManager = [[ZYAudioManager alloc] init];
    [managers addObject:tempManager];
  });
   
  dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    ZYAudioManager *tempManager = [[ZYAudioManager alloc] init];
    [managers addObject:tempManager];
  });
   
  dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    ZYAudioManager *tempManager = [[ZYAudioManager alloc] init];
    [managers addObject:tempManager];
  });
   
  dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    ZYAudioManager *tempManager = [[ZYAudioManager alloc] init];
    [managers addObject:tempManager];
  });
   
  dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    ZYAudioManager *tempManager = [[ZYAudioManager alloc] init];
    [managers addObject:tempManager];
  });
   
  ZYAudioManager *managerOne = [ZYAudioManager defaultManager];
   
  dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     
    [managers enumerateObjectsUsingBlock:^(ZYAudioManager *obj, NSUInteger idx, BOOL * _Nonnull stop) {
      XCTAssertEqual(managerOne, obj, @"ZYAudioManager is not single");
    }];
     
  });
}
 
/**
 * 測(cè)試是否可以正常播放音樂
 */
- (void)testPlayingMusic
{
  self.player = [[ZYAudioManager defaultManager] playingMusic:_fileName];
  XCTAssertTrue(self.player.playing, @"ZYAudioManager is not PlayingMusic");
}
 
/**
 * 測(cè)試是否可以正常停止音樂
 */
- (void)testStopMusic
{
  if (self.player == nil) {
    self.player = [[ZYAudioManager defaultManager] playingMusic:_fileName];
  }
   
  if (self.player.playing == NO) [self.player play];
   
  [[ZYAudioManager defaultManager] stopMusic:_fileName];
  XCTAssertFalse(self.player.playing, @"ZYAudioManager is not StopMusic");
}
 
/**
 * 測(cè)試是否可以正常暫停音樂
 */
- (void)testPauseMusic
{
  if (self.player == nil) {
    self.player = [[ZYAudioManager defaultManager] playingMusic:_fileName];
  }
  if (self.player.playing == NO) [self.player play];
  [[ZYAudioManager defaultManager] pauseMusic:_fileName];
  XCTAssertFalse(self.player.playing, @"ZYAudioManager is not pauseMusic");
}
 
@end

需要注意的是,單例要在并發(fā)的條件下測(cè)試,我采用的是dispatch_group,主要是考慮到,必須要等待所有并發(fā)結(jié)束才能比較結(jié)果,否則可能會(huì)出錯(cuò)。比如說,并發(fā)條件下,x線程已經(jīng)執(zhí)行完畢了,它所對(duì)應(yīng)的a對(duì)象已有值;而y線程還沒開始初始化,它所對(duì)應(yīng)的b對(duì)象還是為nil,為了避免這種條件的產(chǎn)生,我采用dispatch_group來等待所有并發(fā)結(jié)束,再去做相應(yīng)的判斷。

首頁(yè)控制器的代碼:

 #import "ZYMusicViewController.h"
#import "ZYPlayingViewController.h"
#import "ZYMusicTool.h"
#import "ZYMusic.h"
#import "ZYMusicCell.h"
 
@interface ZYMusicViewController ()
@property (nonatomic, strong) ZYPlayingViewController *playingVc;
 
@property (nonatomic, assign) int currentIndex;
@end
 
@implementation ZYMusicViewController
 
- (ZYPlayingViewController *)playingVc
{
  if (_playingVc == nil) {
    _playingVc = [[ZYPlayingViewController alloc] initWithNibName:@"ZYPlayingViewController" bundle:nil];
  }
  return _playingVc;
}
 
- (void)viewDidLoad {
  [super viewDidLoad];
   
  [self setupNavigation];
}
 
- (void)setupNavigation
{
  self.navigationItem.title = @"音樂播放器";
}
 
#pragma mark ----TableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
 
  return 1;
}
 
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  return [ZYMusicTool musics].count;
}
 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  ZYMusicCell *cell = [ZYMusicCell musicCellWithTableView:tableView];
  cell.music = [ZYMusicTool musics][indexPath.row];
  return cell;
}
 
#pragma mark ----TableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
  return 70;
}
 
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
  [tableView deselectRowAtIndexPath:indexPath animated:YES];
   
  [ZYMusicTool setPlayingMusic:[ZYMusicTool musics][indexPath.row]];
   
  ZYMusic *preMusic = [ZYMusicTool musics][self.currentIndex];
  preMusic.playing = NO;
  ZYMusic *music = [ZYMusicTool musics][indexPath.row];
  music.playing = YES;
  NSArray *indexPaths = @[
              [NSIndexPath indexPathForItem:self.currentIndex inSection:0],
              indexPath
              ];
  [self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
   
  self.currentIndex = (int)indexPath.row;
   
  [self.playingVc show];
}
 
@end 

重點(diǎn)需要說說的是這個(gè)界面的實(shí)現(xiàn):

 這里做了比較多的細(xì)節(jié)控制,具體在代碼里面有相應(yīng)的描述。主要是想說說,在實(shí)現(xiàn)播放進(jìn)度拖拽中遇到的問題。

控制進(jìn)度條的移動(dòng),我采用的是NSTimer,添加了一個(gè)定時(shí)器,并且在不需要它的地方都做了相應(yīng)的移除操作。

這里開發(fā)的時(shí)候,遇到了一個(gè)問題是,我拖動(dòng)滑塊的時(shí)候,發(fā)現(xiàn)歌曲播放的進(jìn)度是不正確的。代碼中可以看到:

//得到挪動(dòng)距離
  CGPoint point = [sender translationInView:sender.view];
  //將translation清空,免得重復(fù)疊加
  [sender setTranslation:CGPointZero inView:sender.view];

 在使用translation的時(shí)候,一定要記住,每次處理過后,一定要將translation清空,以免它不斷疊加。

我使用的是ZYLrcView來展示歌詞界面的,需要注意的是,它繼承自UIImageView,所以要將userInteractionEnabled屬性設(shè)置為Yes。

代碼:

#import <UIKit/UIKit.h>
 
@interface ZYLrcView : UIImageView
@property (nonatomic, assign) NSTimeInterval currentTime;
@property (nonatomic, copy) NSString *fileName;
@end
 
 
 
#import "ZYLrcView.h"
#import "ZYLrcLine.h"
#import "ZYLrcCell.h"
#import "UIView+AutoLayout.h"
 
@interface ZYLrcView () <UITableViewDataSource, UITableViewDelegate>
@property (nonatomic, weak) UITableView *tableView;
@property (nonatomic, strong) NSMutableArray *lrcLines;
/**
 * 記錄當(dāng)前顯示歌詞在數(shù)組里面的index
 */
@property (nonatomic, assign) int currentIndex;
@end
 
@implementation ZYLrcView
 
#pragma mark ----setter\geter方法
 
- (NSMutableArray *)lrcLines
{
  if (_lrcLines == nil) {
    _lrcLines = [ZYLrcLine lrcLinesWithFileName:self.fileName];
  }
  return _lrcLines;
}
 
- (void)setFileName:(NSString *)fileName
{
  if ([_fileName isEqualToString:fileName]) {
    return;
  }
  _fileName = [fileName copy];
  [_lrcLines removeAllObjects];
  _lrcLines = nil;
  [self.tableView reloadData];
}
 
- (void)setCurrentTime:(NSTimeInterval)currentTime
{
  if (_currentTime > currentTime) {
    self.currentIndex = 0;
  }
  _currentTime = currentTime;
   
  int minute = currentTime / 60;
  int second = (int)currentTime % 60;
  int msecond = (currentTime - (int)currentTime) * 100;
  NSString *currentTimeStr = [NSString stringWithFormat:@"%02d:%02d.%02d", minute, second, msecond];
   
  for (int i = self.currentIndex; i < self.lrcLines.count; i++) {
    ZYLrcLine *currentLine = self.lrcLines[i];
    NSString *currentLineTime = currentLine.time;
    NSString *nextLineTime = nil;
     
    if (i + 1 < self.lrcLines.count) {
      ZYLrcLine *nextLine = self.lrcLines[i + 1];
      nextLineTime = nextLine.time;
    }
     
    if (([currentTimeStr compare:currentLineTime] != NSOrderedAscending) && ([currentTimeStr compare:nextLineTime] == NSOrderedAscending) && (self.currentIndex != i)) {
       
       
      NSArray *reloadLines = @[
                   [NSIndexPath indexPathForItem:self.currentIndex inSection:0],
                   [NSIndexPath indexPathForItem:i inSection:0]
                   ];
      self.currentIndex = i;
      [self.tableView reloadRowsAtIndexPaths:reloadLines withRowAnimation:UITableViewRowAnimationNone];
       
       
      [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:self.currentIndex inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
    }
     
  }
}
#pragma mark ----初始化方法
 
- (instancetype)initWithFrame:(CGRect)frame
{
  if (self = [super initWithFrame:frame]) {
    [self commitInit];
  }
  return self;
}
 
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
  if (self = [super initWithCoder:aDecoder]) {
    [self commitInit];
  }
  return self;
}
 
- (void)commitInit
{
  self.userInteractionEnabled = YES;
  self.image = [UIImage imageNamed:@"28131977_1383101943208"];
  self.contentMode = UIViewContentModeScaleToFill;
  self.clipsToBounds = YES;
  UITableView *tableView = [[UITableView alloc] init];
  tableView.delegate = self;
  tableView.dataSource = self;
  tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
  tableView.backgroundColor = [UIColor clearColor];
  self.tableView = tableView;
  [self addSubview:tableView];
  [self.tableView autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
}
 
#pragma mark ----UITableViewDataSource
 
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
  return 1;
}
 
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
  return self.lrcLines.count;
}
 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  ZYLrcCell *cell = [ZYLrcCell lrcCellWithTableView:tableView];
  cell.lrcLine = self.lrcLines[indexPath.row];
   
  if (indexPath.row == self.currentIndex) {
     
    cell.textLabel.font = [UIFont boldSystemFontOfSize:16];
  }
  else{
    cell.textLabel.font = [UIFont systemFontOfSize:13];
  }
  return cell;
}
 
- (void)layoutSubviews
{
  [super layoutSubviews];
   
//  NSLog(@"++++++++++%@",NSStringFromCGRect(self.tableView.frame));
  self.tableView.contentInset = UIEdgeInsetsMake(self.frame.size.height / 2, 0, self.frame.size.height / 2, 0);
}
@end

 也沒有什么好說的,整體思路就是,解析歌詞,將歌詞對(duì)應(yīng)的播放時(shí)間、在當(dāng)前播放時(shí)間的那句歌詞一一對(duì)應(yīng),然后持有一個(gè)歌詞播放的定時(shí)器,每次給ZYLrcView傳入歌曲播放的當(dāng)前時(shí)間,如果,歌曲的currentTime > 當(dāng)前歌詞的播放,并且小于下一句歌詞的播放時(shí)間,那么就是播放當(dāng)前的這一句歌詞了。

我這里做了相應(yīng)的優(yōu)化,CADisplayLink生成的定時(shí)器,是每毫秒調(diào)用觸發(fā)一次,1s等于1000ms,如果不做一定的優(yōu)化,性能是非常差的,畢竟一首歌怎么也有四五分鐘。在這里,我記錄了上一句歌詞的index,那么如果正常播放的話,它去查找歌詞應(yīng)該是從上一句播放的歌詞在數(shù)組里面的索引開始查找,這樣就優(yōu)化了很多。

這是鎖屏下的界面展示:

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • iOS中CPU線程調(diào)試的高級(jí)技巧分享

    iOS中CPU線程調(diào)試的高級(jí)技巧分享

    這篇文章主要給大家介紹了關(guān)于iOS中CPU線程調(diào)試的高級(jí)技巧,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-11-11
  • IOS 線程死鎖詳細(xì)介紹

    IOS 線程死鎖詳細(xì)介紹

    這篇文章主要介紹了IOS 線程死鎖詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下
    2017-02-02
  • iOS開發(fā)實(shí)現(xiàn)簡(jiǎn)單抽屜效果

    iOS開發(fā)實(shí)現(xiàn)簡(jiǎn)單抽屜效果

    這篇文章主要為大家詳細(xì)介紹了iOS開發(fā)實(shí)現(xiàn)簡(jiǎn)單抽屜效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • iOS11 下載之?dāng)帱c(diǎn)續(xù)傳的bug的解決方法

    iOS11 下載之?dāng)帱c(diǎn)續(xù)傳的bug的解決方法

    本篇文章主要介紹了iOS11 下載之?dāng)帱c(diǎn)續(xù)傳的bug的解決方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-11-11
  • 舉例詳解iOS開發(fā)過程中的沙盒機(jī)制與文件

    舉例詳解iOS開發(fā)過程中的沙盒機(jī)制與文件

    這篇文章主要介紹了舉例詳解iOS開發(fā)過程中的沙盒機(jī)制與文件,示例代碼為傳統(tǒng)的Obejective-C,需要的朋友可以參考下
    2015-09-09
  • iOS 動(dòng)畫實(shí)戰(zhàn)之釣魚小游戲?qū)嵗a

    iOS 動(dòng)畫實(shí)戰(zhàn)之釣魚小游戲?qū)嵗a

    最近小編做了一個(gè)釣魚小游戲,平時(shí)沒有做過,所以上手有點(diǎn)急躁,不過,最終還是實(shí)現(xiàn)了,下面小編給大家分享iOS 動(dòng)畫實(shí)戰(zhàn)之釣魚小游戲的實(shí)現(xiàn)思路,感興趣的朋友一起看看吧
    2018-02-02
  • iOS實(shí)現(xiàn)選項(xiàng)卡效果的方法

    iOS實(shí)現(xiàn)選項(xiàng)卡效果的方法

    選項(xiàng)卡在我們?nèi)粘i_發(fā)的時(shí)候經(jīng)常要用到,所以這篇文章給大家分享一種iOS實(shí)現(xiàn)的簡(jiǎn)單選項(xiàng)卡效果,很適合大家學(xué)習(xí)和使用,有需要的可以參考借鑒,下面來一起看看吧。
    2016-09-09
  • 總結(jié)iOS開發(fā)中的斷點(diǎn)續(xù)傳與實(shí)踐

    總結(jié)iOS開發(fā)中的斷點(diǎn)續(xù)傳與實(shí)踐

    本文先從斷點(diǎn)續(xù)傳問題開始,介紹斷點(diǎn)續(xù)傳概述和原理。接著結(jié)合筆者調(diào)研中嘗試的 AFHTTPRequestOpeartion,簡(jiǎn)單分析源碼。最后分別基于 NSURLConnection,NSURLSessionDataTask 和 NSURLSessionDownloadTask 去實(shí)現(xiàn)應(yīng)用重啟情況下的斷點(diǎn)續(xù)傳。下面一起來看看。
    2016-07-07
  • iOS輸入框的字?jǐn)?shù)統(tǒng)計(jì)/最大長(zhǎng)度限制詳解

    iOS輸入框的字?jǐn)?shù)統(tǒng)計(jì)/最大長(zhǎng)度限制詳解

    在開發(fā)中經(jīng)常會(huì)遇到鍵盤輸入的字符長(zhǎng)度的限制,下面這篇文章主要給大家介紹了關(guān)于iOS輸入框的字?jǐn)?shù)統(tǒng)計(jì)/最大長(zhǎng)度限制的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-06-06
  • iOS實(shí)現(xiàn)按鈕點(diǎn)擊選中與被選中切換功能

    iOS實(shí)現(xiàn)按鈕點(diǎn)擊選中與被選中切換功能

    這篇文章主要介紹了iOS實(shí)現(xiàn)按鈕點(diǎn)擊選中與被選中切換功能,需要的朋友可以參考下
    2017-07-07

最新評(píng)論