├── screenshots └── QHDanmuShow.gif ├── QHDanumuDemo.xcodeproj ├── xcuserdata │ └── chen.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ ├── xcschememanagement.plist │ │ └── QHDanumuDemo.xcscheme ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcuserdata │ │ └── chen.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcshareddata │ │ └── QHDanumuDemo.xccheckout └── project.pbxproj ├── QHDanmuControl ├── QHDanmu │ ├── QHDanmuView.h │ ├── QHDanmuView.m │ ├── QHDanmuUtil.h │ ├── QHDanmuLabel.h │ ├── QHDanmuManager.h │ ├── QHDanmuUtil.m │ ├── QHDanmuLabel.m │ └── QHDanmuManager.m └── QHDanmuSend │ ├── QHDanmuSendView.h │ ├── QHDanmuOperateView.h │ ├── QHDanmuOperateView.m │ └── QHDanmuSendView.m ├── QHDanumuDemo ├── QHDanmu │ ├── QHDanmuView.h │ ├── QHDanmuView.m │ ├── QHDanmuUtil.h │ ├── QHDanmuLabel.h │ ├── QHDanmuManager.h │ ├── QHDanmuUtil.m │ ├── QHDanmuLabel.m │ └── QHDanmuManager.m ├── ViewController.h ├── AppDelegate.h ├── main.m ├── NSTimer+EOCBlocksSupport.h ├── QHDanmuSend │ ├── QHDanmuSendView.h │ ├── QHDanmuOperateView.h │ ├── QHDanmuOperateView.m │ └── QHDanmuSendView.m ├── NSTimer+EOCBlocksSupport.m ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── AppDelegate.m ├── QHDanmuSource.plist ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard └── ViewController.m ├── QHDanumuDemo.podspec ├── QHDanumuDemoTests ├── Info.plist └── QHDanumuDemoTests.m ├── LICENSE └── README.md /screenshots/QHDanmuShow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenqihui/QHDanumuDemo/HEAD/screenshots/QHDanmuShow.gif -------------------------------------------------------------------------------- /QHDanumuDemo.xcodeproj/xcuserdata/chen.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /QHDanumuDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /QHDanumuDemo.xcodeproj/project.xcworkspace/xcuserdata/chen.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenqihui/QHDanumuDemo/HEAD/QHDanumuDemo.xcodeproj/project.xcworkspace/xcuserdata/chen.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /QHDanmuControl/QHDanmu/QHDanmuView.h: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuView.h 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/6/28. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface QHDanmuView : UIView 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /QHDanumuDemo/QHDanmu/QHDanmuView.h: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuView.h 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/6/28. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface QHDanmuView : UIView 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /QHDanumuDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/6/28. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /QHDanumuDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/6/28. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /QHDanumuDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/6/28. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /QHDanumuDemo/NSTimer+EOCBlocksSupport.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSTimer+EOCBlocksSupport.h 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/7/10. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSTimer (EOCBlocksSupport) 12 | 13 | + (NSTimer *)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void (^)())block repeats:(BOOL)repeats; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /QHDanmuControl/QHDanmu/QHDanmuView.m: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuView.m 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/6/28. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import "QHDanmuView.h" 10 | 11 | @implementation QHDanmuView 12 | 13 | - (instancetype)initWithFrame:(CGRect)frame { 14 | self = [super initWithFrame:frame]; 15 | if (self) { 16 | self.backgroundColor = [UIColor clearColor]; 17 | self.clipsToBounds = YES; 18 | } 19 | return self; 20 | } 21 | 22 | #pragma mark - Private 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /QHDanumuDemo/QHDanmu/QHDanmuView.m: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuView.m 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/6/28. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import "QHDanmuView.h" 10 | 11 | @implementation QHDanmuView 12 | 13 | - (instancetype)initWithFrame:(CGRect)frame { 14 | self = [super initWithFrame:frame]; 15 | if (self) { 16 | self.backgroundColor = [UIColor clearColor]; 17 | self.clipsToBounds = YES; 18 | } 19 | return self; 20 | } 21 | 22 | #pragma mark - Private 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /QHDanumuDemo.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "QHDanumuDemo" 3 | s.version = "1.4" 4 | s.summary = "QHDanmuControl 是一款视频常用的弹幕系统算法。" 5 | s.homepage = "https://github.com/chenqihui/QHDanumuDemo" 6 | s.license = "MIT" 7 | s.authors = { "Ryuuku" => "chen_qihui@qq.com" } 8 | s.frameworks = 'Foundation', 'UIKit' 9 | s.platform = :ios, '7.0' 10 | s.source = { :git => "https://github.com/chenqihui/QHDanumuDemo.git", :tag => "1.4" } 11 | s.source_files = 'QHDanmuControl/**/*.{h,m}' 12 | s.requires_arc = true 13 | end 14 | -------------------------------------------------------------------------------- /QHDanmuControl/QHDanmuSend/QHDanmuSendView.h: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuSendView.h 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/7/8. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class QHDanmuSendView; 12 | 13 | @protocol QHDanmuSendViewDelegate 14 | 15 | @optional 16 | 17 | - (void)sendDanmu:(QHDanmuSendView *)danmuSendV info:(NSString *)info; 18 | 19 | - (void)closeSendDanmu:(QHDanmuSendView *)danmuSendV; 20 | 21 | @end 22 | 23 | @interface QHDanmuSendView : UIView 24 | 25 | @property (nonatomic, weak) id deleagte; 26 | 27 | - (void)showAction:(UIView *)superView; 28 | 29 | - (void)backAction; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /QHDanumuDemo/QHDanmuSend/QHDanmuSendView.h: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuSendView.h 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/7/8. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class QHDanmuSendView; 12 | 13 | @protocol QHDanmuSendViewDelegate 14 | 15 | @optional 16 | 17 | - (void)sendDanmu:(QHDanmuSendView *)danmuSendV info:(NSString *)info; 18 | 19 | - (void)closeSendDanmu:(QHDanmuSendView *)danmuSendV; 20 | 21 | @end 22 | 23 | @interface QHDanmuSendView : UIView 24 | 25 | @property (nonatomic, weak) id deleagte; 26 | 27 | - (void)showAction:(UIView *)superView; 28 | 29 | - (void)backAction; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /QHDanmuControl/QHDanmuSend/QHDanmuOperateView.h: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuOperateView.h 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/7/8. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol QHDanmuOperateViewDelegate 12 | 13 | - (void)closeDanmu:(UIButton *)btn; 14 | 15 | @end 16 | 17 | @interface QHDanmuOperateView : UIView 18 | 19 | @property (nonatomic, strong, readonly) UITextField *editContentTF; 20 | 21 | @property (nonatomic, strong, readonly) UIButton *sendBtn; 22 | 23 | @property (nonatomic, weak) id deleagte; 24 | 25 | //- (void)setOperateHeight:(CGFloat)h; 26 | // 27 | //- (void)setEditHeight:(CGFloat)h; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /QHDanumuDemo/QHDanmuSend/QHDanmuOperateView.h: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuOperateView.h 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/7/8. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol QHDanmuOperateViewDelegate 12 | 13 | - (void)closeDanmu:(UIButton *)btn; 14 | 15 | @end 16 | 17 | @interface QHDanmuOperateView : UIView 18 | 19 | @property (nonatomic, strong, readonly) UITextField *editContentTF; 20 | 21 | @property (nonatomic, strong, readonly) UIButton *sendBtn; 22 | 23 | @property (nonatomic, weak) id deleagte; 24 | 25 | //- (void)setOperateHeight:(CGFloat)h; 26 | // 27 | //- (void)setEditHeight:(CGFloat)h; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /QHDanumuDemo/NSTimer+EOCBlocksSupport.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSTimer+EOCBlocksSupport.m 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/7/10. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import "NSTimer+EOCBlocksSupport.h" 10 | 11 | @implementation NSTimer (EOCBlocksSupport) 12 | 13 | + (NSTimer *)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void (^)())block repeats:(BOOL)repeats { 14 | return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(eoc_blockInvoke:) userInfo:[block copy] repeats:repeats]; 15 | } 16 | 17 | + (void)eoc_blockInvoke:(NSTimer *)timer { 18 | void (^block)() = timer.userInfo; 19 | if (block) { 20 | block(); 21 | } 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /QHDanumuDemo.xcodeproj/xcuserdata/chen.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | QHDanumuDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 7448717B1B3F7E0700DB22B7 16 | 17 | primary 18 | 19 | 20 | 744871941B3F7E0700DB22B7 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /QHDanumuDemo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /QHDanumuDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /QHDanumuDemoTests/QHDanumuDemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanumuDemoTests.m 3 | // QHDanumuDemoTests 4 | // 5 | // Created by chen on 15/6/28. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface QHDanumuDemoTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation QHDanumuDemoTests 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testExample { 29 | // This is an example of a functional test case. 30 | XCTAssert(YES, @"Pass"); 31 | } 32 | 33 | - (void)testPerformanceExample { 34 | // This is an example of a performance test case. 35 | [self measureBlock:^{ 36 | // Put the code you want to measure the time of here. 37 | }]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /QHDanumuDemo/QHDanmu/QHDanmuUtil.h: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuUtil.h 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/6/28. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | typedef NS_ENUM(NSUInteger, DanmuState) { 13 | DanmuStateStop = 1, 14 | DanmuStateAnimationing, 15 | DanmuStateFinish 16 | }; 17 | 18 | #define CHANNEL_HEIGHT 25 19 | 20 | /** 21 | * 弹幕属性key 22 | */ 23 | extern NSString * const kDanmuContentKey;//弹幕内容 24 | extern NSString * const kDanmuTimeKey;//视频时间 25 | extern NSString * const kDanmuOptionalKey;//弹幕样式 26 | 27 | @interface QHDanmuUtil : NSObject 28 | 29 | + (CGSize)getSizeWithString:(NSString *)str withFont:(UIFont *)font size:(CGSize)size; 30 | 31 | + (UIColor *)colorFromARGB:(int)argb; 32 | /** 33 | * t:df(使用默认样式,样式替换成dfopt中的样式) 34 | * 35 | * @return 36 | */ 37 | + (NSDictionary *)defaultOptions; 38 | /** 39 | * 随机样式 40 | * 41 | * @return 42 | */ 43 | + (NSDictionary *)randomOptions; 44 | /** 45 | * 横竖屏判断,是否横屏 46 | * 47 | * @return 48 | */ 49 | + (BOOL)isOrientationLandscape; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /QHDanmuControl/QHDanmu/QHDanmuUtil.h: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuUtil.h 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/6/28. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | typedef NS_ENUM(NSUInteger, DanmuState) { 13 | DanmuStateStop = 1, 14 | DanmuStateAnimationing, 15 | DanmuStateFinish 16 | }; 17 | 18 | #define CHANNEL_HEIGHT 25 19 | 20 | /** 21 | * 弹幕属性key 22 | */ 23 | extern NSString * const kDanmuContentKey;//弹幕内容 24 | extern NSString * const kDanmuTimeKey;//视频时间 25 | extern NSString * const kDanmuOptionalKey;//弹幕样式 26 | 27 | @interface QHDanmuUtil : NSObject 28 | 29 | + (CGSize)getSizeWithString:(NSString *)str withFont:(UIFont *)font size:(CGSize)size; 30 | 31 | + (UIColor *)colorFromARGB:(int)argb; 32 | /** 33 | * t:df(使用默认样式,样式替换成dfopt中的样式) 34 | * 35 | * @return 36 | */ 37 | + (NSDictionary *)defaultOptions; 38 | /** 39 | * 随机样式 40 | * 41 | * @return 42 | */ 43 | + (NSDictionary *)randomOptions; 44 | /** 45 | * 横竖屏判断,是否横屏 46 | * 47 | * @return 48 | */ 49 | + (BOOL)isOrientationLandscape; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 QiHui Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /QHDanumuDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /QHDanumuDemo.xcodeproj/project.xcworkspace/xcshareddata/QHDanumuDemo.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 22BD8EE6-FA43-4C46-918C-5A43CECF7789 9 | IDESourceControlProjectName 10 | QHDanumuDemo 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 747A28C498662C49505AA0A64AD6EE73F4AA9555 14 | https://github.com/chenqihui/QHDanumuDemo.git 15 | 16 | IDESourceControlProjectPath 17 | QHDanumuDemo.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 747A28C498662C49505AA0A64AD6EE73F4AA9555 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/chenqihui/QHDanumuDemo.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 747A28C498662C49505AA0A64AD6EE73F4AA9555 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 747A28C498662C49505AA0A64AD6EE73F4AA9555 36 | IDESourceControlWCCName 37 | QHDanumuDemo 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /QHDanmuControl/QHDanmu/QHDanmuLabel.h: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuLabel.h 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/7/2. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "QHDanmuUtil.h" 12 | 13 | @interface QHDanmuLabel : UILabel 14 | 15 | @property (nonatomic, strong, readonly) NSDictionary *info; 16 | @property (nonatomic, readonly) DanmuState danmuState; 17 | @property (nonatomic, readonly) CGFloat animationDuartion; 18 | @property (nonatomic, strong, readonly) NSDictionary *dicOptional; 19 | 20 | @property (nonatomic, readonly) CGFloat speed; 21 | @property (nonatomic, readonly) CGFloat currentRightX; 22 | 23 | @property (nonatomic, readonly) CGFloat startTime; 24 | /** 25 | * 获取对应属性 26 | */ 27 | @property (nonatomic, getter=isFontSizeBig, readonly) BOOL fontSizeBig; 28 | @property (nonatomic, getter=isFontSizeMiddle, readonly) BOOL fontSizeMiddle; 29 | @property (nonatomic, getter=isFontSizeSmall, readonly) BOOL fontSizeSmall; 30 | @property (nonatomic, getter=isMoveModeRolling, readonly) BOOL moveModeRolling; 31 | @property (nonatomic, getter=isMoveModeFadeOut, readonly) BOOL moveModeFadeOut; 32 | @property (nonatomic, getter=isPositionTop, readonly) BOOL positionTop; 33 | @property (nonatomic, getter=isPositionMiddle, readonly) BOOL positionMiddle; 34 | @property (nonatomic, getter=isPositionBottom, readonly) BOOL positionBottom; 35 | 36 | + (instancetype)createWithInfo:(NSDictionary *)info inView:(UIView *)view; 37 | 38 | - (void)setDanmuChannel:(NSUInteger)channel offset:(CGFloat)xy; 39 | 40 | - (void)animationDanmuItem:(NSTimeInterval)waitTime; 41 | 42 | - (void)pause; 43 | 44 | - (void)resume:(NSTimeInterval)nowTime; 45 | 46 | - (void)removeDanmu; 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /QHDanumuDemo/QHDanmu/QHDanmuLabel.h: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuLabel.h 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/7/2. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "QHDanmuUtil.h" 12 | 13 | @interface QHDanmuLabel : UILabel 14 | 15 | @property (nonatomic, strong, readonly) NSDictionary *info; 16 | @property (nonatomic, readonly) DanmuState danmuState; 17 | @property (nonatomic, readonly) CGFloat animationDuartion; 18 | @property (nonatomic, strong, readonly) NSDictionary *dicOptional; 19 | 20 | @property (nonatomic, readonly) CGFloat speed; 21 | @property (nonatomic, readonly) CGFloat currentRightX; 22 | 23 | @property (nonatomic, readonly) CGFloat startTime; 24 | /** 25 | * 获取对应属性 26 | */ 27 | @property (nonatomic, getter=isFontSizeBig, readonly) BOOL fontSizeBig; 28 | @property (nonatomic, getter=isFontSizeMiddle, readonly) BOOL fontSizeMiddle; 29 | @property (nonatomic, getter=isFontSizeSmall, readonly) BOOL fontSizeSmall; 30 | @property (nonatomic, getter=isMoveModeRolling, readonly) BOOL moveModeRolling; 31 | @property (nonatomic, getter=isMoveModeFadeOut, readonly) BOOL moveModeFadeOut; 32 | @property (nonatomic, getter=isPositionTop, readonly) BOOL positionTop; 33 | @property (nonatomic, getter=isPositionMiddle, readonly) BOOL positionMiddle; 34 | @property (nonatomic, getter=isPositionBottom, readonly) BOOL positionBottom; 35 | 36 | + (instancetype)createWithInfo:(NSDictionary *)info inView:(UIView *)view; 37 | 38 | - (void)setDanmuChannel:(NSUInteger)channel offset:(CGFloat)xy; 39 | 40 | - (void)animationDanmuItem:(NSTimeInterval)waitTime; 41 | 42 | - (void)pause; 43 | 44 | - (void)resume:(NSTimeInterval)nowTime; 45 | 46 | - (void)removeDanmu; 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # **建议参考新的弹幕系统:[chenqihui/QHDanmu2Demo](https://github.com/chenqihui/QHDanmu2Demo)** 3 | 4 | # 说明: 5 | 6 | QHDanmu文件夹下是主要的弹幕模块系统 7 | 8 | QHDanmuSend文件夹下是简单的发射弹幕的界面 9 | 10 | 使用可以参考ViewController 11 | 12 | 创建弹幕 13 | 14 | self.danmuManager = [[QHDanmuManager alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, _screenV.bounds.size.height) data:infos inView:_screenV durationTime:1]; 15 | 16 | [self.danmuManager initStart]; 17 | 18 | 发射弹幕 19 | 20 | self.danmuSendV = [[QHDanmuSendView alloc] initWithFrame:self.view.bounds]; 21 | 22 | [self.view addSubview:self.danmuSendV]; 23 | 24 | self.danmuSendV.deleagte = self; 25 | 26 | [self.danmuSendV showAction:self.view]; 27 | 28 | # 安装(通过CocoaPods): 29 | 30 | pod "QHDanumuDemo", '~> 1.4' 31 | 32 | pod "QHDanumuDemo", '~> 1.3' 33 | 34 | # 效果图: 35 | 36 | ![image](https://github.com/chenqihui/QHDanumuDemo/blob/master/screenshots/QHDanmuShow.gif) 37 | 38 | # 描述: 39 | 40 | 一、固定的数值(可自行修改): 41 | 42 | 1、字体大小 大中小分别为19、17、15 43 | 44 | 2、航道高度 高度为25 45 | 46 | 3、航道缓冲区宽度 宽度为120 47 | 48 | 4、航行总时间 时间为5秒 49 | 50 | 5、当需要第2种航道选择时的间隔距离 距离为20 51 | 52 | 53 | 二、滑动航道选择 54 | 55 | 方案: 56 | 57 | 1、 58 | 59 | 通过弹幕碰撞检测,决定是否使用此航道,即航道每次都是从上往下做判断。 60 | 61 | 碰撞检测主要难点在于检测横向滚动弹幕之间的碰撞,弹幕存活时间由其显示时间和存活长短决定,因此,弹幕之间是否碰撞只需检测开始和消失是否碰撞即可。 62 | 63 | 这个参考[iOS弹幕(源码)实现原理解析](http://www.olinone.com/?p=186) 64 | 65 | 2、 66 | 67 | 当第一个找不到航道时候,检查所有航道最小距离,这个距离必须在指定的最大弹幕的长度之内,如果找到,将其放置在对于弹幕后面。 68 | 69 | 2.1、前弹幕最右边还没出现在屏幕时,新弹幕放置到其后面,space为俩之间间隔 70 | 71 | 2.2、前弹幕最右边已出现在屏幕时,新弹幕仍然放置边界等待滚动 72 | 73 | 浮现航道选择(分为两排航道) 74 | 75 | 方案: 76 | 77 | 1、 78 | 79 | 选择第一排,按没有弹幕为准,没有就显示 80 | 81 | 2、 82 | 83 | 第一排都占满,使用第二排,第二排是在第一排的基础坐标y向下半个航道高度, 84 | 85 | 这样可以有个视觉差,第一排显示消失时,可以看到第二排,从而争取更大的显示航道(2n-1) 86 | 87 | 88 | 三、其他 89 | 90 | 1、弹幕字体大小、颜色和运动模式都是随机的。 91 | 92 | 2、支持横竖屏 93 | -------------------------------------------------------------------------------- /QHDanumuDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/6/28. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // 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. 25 | // 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. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // 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. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // 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. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // 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. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /QHDanmuControl/QHDanmu/QHDanmuManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuManager.h 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/6/28. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "QHDanmuUtil.h" 12 | 13 | /** 14 | * 弹幕状态 15 | */ 16 | typedef NS_ENUM(NSUInteger, DanmuManagerState) { 17 | /** 18 | * 弹幕准备 19 | */ 20 | DanmuManagerStateWait = 1, 21 | /** 22 | * 弹幕关闭 23 | */ 24 | DanmuManagerStateStop, 25 | /** 26 | * 弹幕运动 27 | */ 28 | DanmuManagerStateAnimationing, 29 | /** 30 | * 弹幕暂停 31 | */ 32 | DanmuManagerStatePause 33 | }; 34 | 35 | @interface QHDanmuManager : NSObject 36 | /** 37 | * 弹幕添加的UIView 38 | */ 39 | @property (nonatomic, strong, readonly) UIView *danmuView; 40 | /** 41 | * 弹幕状态 42 | */ 43 | @property (nonatomic, readonly) DanmuManagerState danmuManagerState; 44 | /** 45 | * 创建弹幕管理对象 46 | * 47 | * @param frame 弹幕显示的frame 48 | * @param infos 弹幕信息,数组集合 49 | * @param view 弹幕添加的UIView 50 | * @param time 弹幕刷新的间隔时间,一般1秒 51 | * 52 | * @return 53 | */ 54 | - (instancetype)initWithFrame:(CGRect)frame data:(NSArray *)infos inView:(UIView *)view durationTime:(NSUInteger)time; 55 | /** 56 | * 弹幕初始化创建 57 | */ 58 | - (void)initStart; 59 | /** 60 | * 弹幕滚动 61 | * 62 | * @param startTime 目前的时间点 63 | */ 64 | - (void)rollDanmu:(NSTimeInterval)startTime; 65 | /** 66 | * 弹幕关闭 67 | */ 68 | - (void)stop; 69 | /** 70 | * 弹幕暂停 71 | */ 72 | - (void)pause; 73 | /** 74 | * 弹幕继续 75 | * 76 | * @param nowTime 现在的时间点 77 | */ 78 | - (void)resume:(NSTimeInterval)nowTime; 79 | /** 80 | * 弹幕重新恢复 81 | */ 82 | - (void)restart; 83 | /** 84 | * 发射弹幕 85 | * 86 | * @param info 弹幕信息,字典集合 87 | */ 88 | - (void)insertDanmu:(NSDictionary *)info; 89 | /** 90 | * 重置弹幕 91 | * 92 | * @param frame 弹幕显示的frame 93 | * @param infos 弹幕信息,数组集合 94 | * @param view 弹幕添加的UIView 95 | * @param time 弹幕刷新的间隔时间,一般1秒 96 | */ 97 | - (void)resetDanmuWithFrame:(CGRect)frame data:(NSArray *)infos inView:(UIView *)view durationTime:(NSUInteger)time; 98 | /** 99 | * 重置弹幕 100 | * 101 | * @param frame 弹幕显示的frame 102 | */ 103 | - (void)resetDanmuWithFrame:(CGRect)frame; 104 | /** 105 | * 重置弹幕信息 106 | * 调用时需要先,如果视频在播放时,需要先暂停,然后再播放 107 | * @param infos 弹幕信息 108 | */ 109 | - (void)resetDanmuInfos:(NSArray *)infos; 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /QHDanumuDemo/QHDanmu/QHDanmuManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuManager.h 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/6/28. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "QHDanmuUtil.h" 12 | 13 | /** 14 | * 弹幕状态 15 | */ 16 | typedef NS_ENUM(NSUInteger, DanmuManagerState) { 17 | /** 18 | * 弹幕准备 19 | */ 20 | DanmuManagerStateWait = 1, 21 | /** 22 | * 弹幕关闭 23 | */ 24 | DanmuManagerStateStop, 25 | /** 26 | * 弹幕运动 27 | */ 28 | DanmuManagerStateAnimationing, 29 | /** 30 | * 弹幕暂停 31 | */ 32 | DanmuManagerStatePause 33 | }; 34 | 35 | @interface QHDanmuManager : NSObject 36 | /** 37 | * 弹幕添加的UIView 38 | */ 39 | @property (nonatomic, strong, readonly) UIView *danmuView; 40 | /** 41 | * 弹幕状态 42 | */ 43 | @property (nonatomic, readonly) DanmuManagerState danmuManagerState; 44 | /** 45 | * 创建弹幕管理对象 46 | * 47 | * @param frame 弹幕显示的frame 48 | * @param infos 弹幕信息,数组集合 49 | * @param view 弹幕添加的UIView 50 | * @param time 弹幕刷新的间隔时间,一般1秒 51 | * 52 | * @return 53 | */ 54 | - (instancetype)initWithFrame:(CGRect)frame data:(NSArray *)infos inView:(UIView *)view durationTime:(NSUInteger)time; 55 | /** 56 | * 弹幕初始化创建 57 | */ 58 | - (void)initStart; 59 | /** 60 | * 弹幕滚动 61 | * 62 | * @param startTime 目前的时间点 63 | */ 64 | - (void)rollDanmu:(NSTimeInterval)startTime; 65 | /** 66 | * 弹幕关闭 67 | */ 68 | - (void)stop; 69 | /** 70 | * 弹幕暂停 71 | */ 72 | - (void)pause; 73 | /** 74 | * 弹幕继续 75 | * 76 | * @param nowTime 现在的时间点 77 | */ 78 | - (void)resume:(NSTimeInterval)nowTime; 79 | /** 80 | * 弹幕重新恢复 81 | */ 82 | - (void)restart; 83 | /** 84 | * 发射弹幕 85 | * 86 | * @param info 弹幕信息,字典集合 87 | */ 88 | - (void)insertDanmu:(NSDictionary *)info; 89 | /** 90 | * 重置弹幕 91 | * 92 | * @param frame 弹幕显示的frame 93 | * @param infos 弹幕信息,数组集合 94 | * @param view 弹幕添加的UIView 95 | * @param time 弹幕刷新的间隔时间,一般1秒 96 | */ 97 | - (void)resetDanmuWithFrame:(CGRect)frame data:(NSArray *)infos inView:(UIView *)view durationTime:(NSUInteger)time; 98 | /** 99 | * 重置弹幕 100 | * 101 | * @param frame 弹幕显示的frame 102 | */ 103 | - (void)resetDanmuWithFrame:(CGRect)frame; 104 | /** 105 | * 重置弹幕信息 106 | * 调用时需要先,如果视频在播放时,需要先暂停,然后再播放 107 | * @param infos 弹幕信息 108 | */ 109 | - (void)resetDanmuInfos:(NSArray *)infos; 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /QHDanumuDemo/QHDanmu/QHDanmuUtil.m: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuUtil.m 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/6/28. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import "QHDanmuUtil.h" 10 | 11 | NSString * const kDanmuContentKey = @"c"; 12 | NSString * const kDanmuTimeKey = @"v"; 13 | NSString * const kDanmuOptionalKey = @"t"; 14 | 15 | @implementation QHDanmuUtil 16 | 17 | + (CGSize)getSizeWithString:(NSString *)str withFont:(UIFont *)font size:(CGSize)size { 18 | CGRect stringRect = [str boundingRectWithSize:size 19 | options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading) 20 | attributes:@{ NSFontAttributeName : font } 21 | context:nil]; 22 | 23 | return stringRect.size; 24 | } 25 | 26 | + (UIColor *)colorFromARGB:(int)argb { 27 | int blue = argb & 0xff; 28 | int green = argb >> 8 & 0xff; 29 | int red = argb >> 16 & 0xff; 30 | //int alpha = argb >> 24 & 0xff; 31 | //NSLog(@"%i, %i, %i", red, green, blue); 32 | return [UIColor colorWithRed:red/255.f green:green/255.f blue:blue/255.f alpha: 1]; 33 | } 34 | 35 | + (long)argbFromHex:(NSString *)hex 36 | { 37 | const char *cStr = [hex cStringUsingEncoding:NSASCIIStringEncoding]; 38 | long x = strtol(cStr + 1, NULL, 16); 39 | return x; 40 | // return [self colorFromARGB: (int)x]; 41 | } 42 | 43 | + (NSDictionary *)defaultOptions { 44 | return @{@"c": [NSNumber numberWithLong:[QHDanmuUtil argbFromHex: @"#ffffff"]], 45 | @"m": @"l", 46 | @"p": @"t", 47 | @"s": @"m", 48 | @"l": @"n" 49 | }; 50 | } 51 | 52 | + (NSDictionary *)randomOptions { 53 | NSUInteger c = arc4random_uniform(4); 54 | NSArray *arColor = @[@"#ffffff", @"#ff0000", @"#00ff00", @"#0000ff"]; 55 | NSUInteger s = arc4random_uniform(3); 56 | NSArray *arFontSize = @[@"l", @"m", @"s"]; 57 | NSUInteger m = arc4random_uniform(4); 58 | NSArray *arMode = @[@"l", @"f", @"l", @"l"]; 59 | return @{@"c": [NSNumber numberWithLong:[QHDanmuUtil argbFromHex:arColor[c]]], 60 | @"m": arMode[m], 61 | @"p": @"t", 62 | @"s": arFontSize[s], 63 | @"l": @"n" 64 | }; 65 | } 66 | 67 | + (BOOL)isOrientationLandscape { 68 | //if (UIDeviceOrientationIsLandscape([[UIDevice currentDevice] orientation])) { 69 | if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) { 70 | return YES; 71 | } else { 72 | return NO; 73 | } 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /QHDanmuControl/QHDanmu/QHDanmuUtil.m: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuUtil.m 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/6/28. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import "QHDanmuUtil.h" 10 | 11 | NSString * const kDanmuContentKey = @"c"; 12 | NSString * const kDanmuTimeKey = @"v"; 13 | NSString * const kDanmuOptionalKey = @"t"; 14 | 15 | @implementation QHDanmuUtil 16 | 17 | + (CGSize)getSizeWithString:(NSString *)str withFont:(UIFont *)font size:(CGSize)size { 18 | CGRect stringRect = [str boundingRectWithSize:size 19 | options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading) 20 | attributes:@{ NSFontAttributeName : font } 21 | context:nil]; 22 | 23 | return stringRect.size; 24 | } 25 | 26 | + (UIColor *)colorFromARGB:(int)argb { 27 | int blue = argb & 0xff; 28 | int green = argb >> 8 & 0xff; 29 | int red = argb >> 16 & 0xff; 30 | //int alpha = argb >> 24 & 0xff; 31 | //NSLog(@"%i, %i, %i", red, green, blue); 32 | return [UIColor colorWithRed:red/255.f green:green/255.f blue:blue/255.f alpha: 1]; 33 | } 34 | 35 | + (long)argbFromHex:(NSString *)hex 36 | { 37 | const char *cStr = [hex cStringUsingEncoding:NSASCIIStringEncoding]; 38 | long x = strtol(cStr + 1, NULL, 16); 39 | return x; 40 | // return [self colorFromARGB: (int)x]; 41 | } 42 | 43 | + (NSDictionary *)defaultOptions { 44 | return @{@"c": [NSNumber numberWithLong:[QHDanmuUtil argbFromHex: @"#ffffff"]], 45 | @"m": @"l", 46 | @"p": @"t", 47 | @"s": @"m", 48 | @"l": @"n" 49 | }; 50 | } 51 | 52 | + (NSDictionary *)randomOptions { 53 | NSUInteger c = arc4random_uniform(4); 54 | NSArray *arColor = @[@"#ffffff", @"#ff0000", @"#00ff00", @"#0000ff"]; 55 | NSUInteger s = arc4random_uniform(3); 56 | NSArray *arFontSize = @[@"l", @"m", @"s"]; 57 | NSUInteger m = arc4random_uniform(4); 58 | NSArray *arMode = @[@"l", @"f", @"l", @"l"]; 59 | return @{@"c": [NSNumber numberWithLong:[QHDanmuUtil argbFromHex:arColor[c]]], 60 | @"m": arMode[m], 61 | @"p": @"t", 62 | @"s": arFontSize[s], 63 | @"l": @"n" 64 | }; 65 | } 66 | 67 | + (BOOL)isOrientationLandscape { 68 | //if (UIDeviceOrientationIsLandscape([[UIDevice currentDevice] orientation])) { 69 | if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) { 70 | return YES; 71 | } else { 72 | return NO; 73 | } 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /QHDanmuControl/QHDanmuSend/QHDanmuOperateView.m: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuOperateView.m 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/7/8. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import "QHDanmuOperateView.h" 10 | 11 | @interface QHDanmuOperateView () { 12 | CGFloat _spaceY; 13 | CGFloat _spaceX; 14 | } 15 | 16 | @property (nonatomic, strong, readwrite) UITextField *editContentTF; 17 | 18 | @property (nonatomic, strong, readwrite) UIButton *sendBtn; 19 | 20 | @end 21 | 22 | @implementation QHDanmuOperateView 23 | 24 | - (void)dealloc { 25 | self.editContentTF = nil; 26 | self.sendBtn = nil; 27 | } 28 | 29 | - (instancetype)initWithFrame:(CGRect)frame { 30 | self = [super initWithFrame:frame]; 31 | if (self) { 32 | [self setup]; 33 | } 34 | return self; 35 | } 36 | 37 | #pragma mark - Private 38 | 39 | - (void)setup { 40 | self.backgroundColor = [UIColor colorWithRed:251/255.f green:251/255.f blue:251/255.f alpha:1]; 41 | self.userInteractionEnabled = YES; 42 | [self p_setupData]; 43 | [self p_addView]; 44 | } 45 | 46 | - (void)p_setupData { 47 | _spaceX = 5; 48 | _spaceY = 5; 49 | } 50 | 51 | - (void)p_addView { 52 | CGFloat btnH = self.frame.size.height - 2*_spaceY; 53 | CGFloat btnW = btnH; 54 | UIFont *font = [UIFont systemFontOfSize:15]; 55 | 56 | self.sendBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 57 | self.sendBtn.frame = CGRectMake(self.frame.size.width - btnH - _spaceX, _spaceY, btnW, btnH); 58 | self.sendBtn.layer.cornerRadius = 3; 59 | self.sendBtn.backgroundColor = [UIColor blueColor]; 60 | [self.sendBtn.titleLabel setFont:font]; 61 | [self.sendBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 62 | [self.sendBtn setTitle:@"关闭" forState:UIControlStateNormal]; 63 | [self addSubview:self.sendBtn]; 64 | [self.sendBtn addTarget:self action:@selector(closeDanmuAction:) forControlEvents:UIControlEventTouchUpInside]; 65 | 66 | self.editContentTF = [[UITextField alloc] initWithFrame:CGRectMake(_spaceX, _spaceY, CGRectGetMinX(self.sendBtn.frame) - 2*_spaceX, btnH)]; 67 | self.editContentTF.layer.cornerRadius = 5; 68 | self.editContentTF.backgroundColor = [UIColor whiteColor]; 69 | self.editContentTF.font = font; 70 | self.editContentTF.returnKeyType = UIReturnKeySend; 71 | self.editContentTF.layer.borderWidth = 1; 72 | self.editContentTF.layer.borderColor = [UIColor colorWithRed:167/255.f green:162/255.f blue:159/255.f alpha:1].CGColor; 73 | self.editContentTF.placeholder = @"请输入弹幕内容"; 74 | self.editContentTF.leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; 75 | self.editContentTF.leftViewMode = UITextFieldViewModeAlways; 76 | self.editContentTF.clearButtonMode = UITextFieldViewModeWhileEditing; 77 | [self addSubview:self.editContentTF]; 78 | } 79 | 80 | #pragma mark - Action 81 | 82 | - (void)closeDanmuAction:(UIButton *)btn { 83 | if ([self.deleagte respondsToSelector:@selector(closeDanmu:)]) { 84 | [self.deleagte closeDanmu:btn]; 85 | } 86 | } 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /QHDanumuDemo/QHDanmuSend/QHDanmuOperateView.m: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuOperateView.m 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/7/8. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import "QHDanmuOperateView.h" 10 | 11 | @interface QHDanmuOperateView () { 12 | CGFloat _spaceY; 13 | CGFloat _spaceX; 14 | } 15 | 16 | @property (nonatomic, strong, readwrite) UITextField *editContentTF; 17 | 18 | @property (nonatomic, strong, readwrite) UIButton *sendBtn; 19 | 20 | @end 21 | 22 | @implementation QHDanmuOperateView 23 | 24 | - (void)dealloc { 25 | self.editContentTF = nil; 26 | self.sendBtn = nil; 27 | } 28 | 29 | - (instancetype)initWithFrame:(CGRect)frame { 30 | self = [super initWithFrame:frame]; 31 | if (self) { 32 | [self setup]; 33 | } 34 | return self; 35 | } 36 | 37 | #pragma mark - Private 38 | 39 | - (void)setup { 40 | self.backgroundColor = [UIColor colorWithRed:251/255.f green:251/255.f blue:251/255.f alpha:1]; 41 | self.userInteractionEnabled = YES; 42 | [self p_setupData]; 43 | [self p_addView]; 44 | } 45 | 46 | - (void)p_setupData { 47 | _spaceX = 5; 48 | _spaceY = 5; 49 | } 50 | 51 | - (void)p_addView { 52 | CGFloat btnH = self.frame.size.height - 2*_spaceY; 53 | CGFloat btnW = btnH; 54 | UIFont *font = [UIFont systemFontOfSize:15]; 55 | 56 | self.sendBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 57 | self.sendBtn.frame = CGRectMake(self.frame.size.width - btnH - _spaceX, _spaceY, btnW, btnH); 58 | self.sendBtn.layer.cornerRadius = 3; 59 | self.sendBtn.backgroundColor = [UIColor blueColor]; 60 | [self.sendBtn.titleLabel setFont:font]; 61 | [self.sendBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; 62 | [self.sendBtn setTitle:@"关闭" forState:UIControlStateNormal]; 63 | [self addSubview:self.sendBtn]; 64 | [self.sendBtn addTarget:self action:@selector(closeDanmuAction:) forControlEvents:UIControlEventTouchUpInside]; 65 | 66 | self.editContentTF = [[UITextField alloc] initWithFrame:CGRectMake(_spaceX, _spaceY, CGRectGetMinX(self.sendBtn.frame) - 2*_spaceX, btnH)]; 67 | self.editContentTF.layer.cornerRadius = 5; 68 | self.editContentTF.backgroundColor = [UIColor whiteColor]; 69 | self.editContentTF.font = font; 70 | self.editContentTF.returnKeyType = UIReturnKeySend; 71 | self.editContentTF.layer.borderWidth = 1; 72 | self.editContentTF.layer.borderColor = [UIColor colorWithRed:167/255.f green:162/255.f blue:159/255.f alpha:1].CGColor; 73 | self.editContentTF.placeholder = @"请输入弹幕内容"; 74 | self.editContentTF.leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)]; 75 | self.editContentTF.leftViewMode = UITextFieldViewModeAlways; 76 | self.editContentTF.clearButtonMode = UITextFieldViewModeWhileEditing; 77 | [self addSubview:self.editContentTF]; 78 | } 79 | 80 | #pragma mark - Action 81 | 82 | - (void)closeDanmuAction:(UIButton *)btn { 83 | if ([self.deleagte respondsToSelector:@selector(closeDanmu:)]) { 84 | [self.deleagte closeDanmu:btn]; 85 | } 86 | } 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /QHDanumuDemo/QHDanmuSource.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | c 7 | hello1 8 | v 9 | 0.5 10 | 11 | 12 | c 13 | hello2 14 | v 15 | 0.8 16 | 17 | 18 | c 19 | hello3 20 | v 21 | 1.3 22 | 23 | 24 | c 25 | hello4hello4hello4hello4hello4hello4hello4hello4hello4hello4hello4hello4 26 | v 27 | 1.6 28 | 29 | 30 | c 31 | hello5 32 | v 33 | 1.8 34 | 35 | 36 | c 37 | hello6hello6hello6hello6hello6hello6hello6hello6hello6hello6hello6hello6hello6hello6hello6hello6hello6 38 | v 39 | 2.1 40 | 41 | 42 | c 43 | hello7hello7hello7hello7hello7hello7hello7hello7hello7hello7hello7hello7hello7 44 | v 45 | 2.2 46 | 47 | 48 | c 49 | hello8hello8hello8hello8hello8hello8hello8hello8hello8hello8hello8hello8hello8hello8hello8 50 | v 51 | 2.7 52 | 53 | 54 | c 55 | hello9 56 | v 57 | 2.9 58 | 59 | 60 | c 61 | hello10 62 | v 63 | 3.3 64 | 65 | 66 | c 67 | hello11 68 | v 69 | 4.2 70 | 71 | 72 | c 73 | hello12 74 | v 75 | 4.6 76 | 77 | 78 | c 79 | hello13 80 | v 81 | 5.1 82 | 83 | 84 | c 85 | hello14hello14hello14hello14hello14hello14hello14hello14hello14hello14 86 | v 87 | 5.6 88 | 89 | 90 | c 91 | hello15 92 | v 93 | 5.8 94 | 95 | 96 | c 97 | hello16 98 | v 99 | 5.9 100 | 101 | 102 | c 103 | hello17hello17hello17hello17hello17hello17hello17hello17hello17hello17hello17hello17hello17 104 | v 105 | 7.2 106 | 107 | 108 | c 109 | hello18 110 | v 111 | 8.3 112 | 113 | 114 | c 115 | hello19 116 | v 117 | 8.9 118 | 119 | 120 | c 121 | hello20 122 | v 123 | 9.3 124 | 125 | 126 | c 127 | hello21 128 | v 129 | 10.4 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /QHDanumuDemo/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /QHDanumuDemo.xcodeproj/xcuserdata/chen.xcuserdatad/xcschemes/QHDanumuDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /QHDanmuControl/QHDanmuSend/QHDanmuSendView.m: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuSendView.m 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/7/8. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import "QHDanmuSendView.h" 10 | 11 | #import "QHDanmuOperateView.h" 12 | 13 | #define VIEW_OPERATE_HEIGHT 44 14 | 15 | @interface QHDanmuSendView () 16 | 17 | @property (nonatomic, strong) QHDanmuOperateView *danmuOperateV; 18 | @property (nonatomic, strong) UIControl *blackOverlay; 19 | 20 | @end 21 | 22 | @implementation QHDanmuSendView 23 | 24 | - (void)dealloc { 25 | [self removeKeyboardNotificationCenter]; 26 | self.danmuOperateV = nil; 27 | self.blackOverlay = nil; 28 | } 29 | 30 | - (instancetype)initWithFrame:(CGRect)frame 31 | { 32 | self = [super initWithFrame:frame]; 33 | if (self) { 34 | // Do any additional setup after loading the view. 35 | self.backgroundColor = [UIColor clearColor]; 36 | self.userInteractionEnabled = YES; 37 | 38 | [self addSubview:self.blackOverlay]; 39 | 40 | self.danmuOperateV = [[QHDanmuOperateView alloc] initWithFrame:CGRectMake(0, self.frame.size.height - VIEW_OPERATE_HEIGHT, self.frame.size.width, VIEW_OPERATE_HEIGHT)]; 41 | self.danmuOperateV.deleagte = self; 42 | self.danmuOperateV.editContentTF.delegate = self; 43 | [self addSubview:self.danmuOperateV]; 44 | 45 | [self addKeyboardNotificationCenter]; 46 | 47 | self.alpha = 0; 48 | } 49 | return self; 50 | } 51 | 52 | #pragma mark - Private 53 | 54 | //重设界面布局 55 | - (void)p_setOperateView:(CGFloat)h { 56 | CGFloat top = h - CGRectGetHeight(self.danmuOperateV.frame); 57 | CGRect frame = self.danmuOperateV.frame; 58 | frame.origin.y = top; 59 | self.danmuOperateV.frame = frame; 60 | } 61 | 62 | #pragma mark - UITextFieldDelegate 63 | 64 | - (BOOL)textFieldShouldReturn:(UITextField *)textField { 65 | if ([self.deleagte respondsToSelector:@selector(sendDanmu:info:)]) { 66 | [self.deleagte sendDanmu:self info:textField.text]; 67 | } 68 | [self backAction]; 69 | return NO; 70 | } 71 | 72 | #pragma mark - QHDanmuOperateViewDelegate 73 | 74 | - (void)closeDanmu:(UIButton *)btn { 75 | [self backAction]; 76 | } 77 | 78 | #pragma mark - Action 79 | 80 | - (void)showAction:(UIView *)superView { 81 | self.alpha = 1; 82 | CGRect frame = self.frame; 83 | frame.origin.y = superView.frame.size.height; 84 | self.frame = frame; 85 | [UIView animateWithDuration:0.6 animations:^{ 86 | CGRect frame = self.frame; 87 | frame.origin.y = superView.frame.size.height - self.frame.size.height; 88 | self.frame = frame; 89 | } completion:^(BOOL finished) { 90 | [self.danmuOperateV.editContentTF becomeFirstResponder]; 91 | }]; 92 | } 93 | 94 | - (void)backAction { 95 | [self.danmuOperateV.editContentTF resignFirstResponder]; 96 | [UIView animateWithDuration:0.6 animations:^{ 97 | CGRect frame = self.frame; 98 | frame.origin.y = self.superview.frame.size.height; 99 | self.frame = frame; 100 | } completion:^(BOOL finished) { 101 | if ([self.deleagte respondsToSelector:@selector(closeSendDanmu:)]) { 102 | [self.deleagte closeSendDanmu:self]; 103 | } 104 | [self removeFromSuperview]; 105 | }]; 106 | } 107 | 108 | #pragma mark keyboardaction 109 | 110 | - (void)addKeyboardNotificationCenter { 111 | // Do any additional setup after loading the view. 112 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; 113 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; 114 | } 115 | 116 | - (void)removeKeyboardNotificationCenter { 117 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil]; 118 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil]; 119 | } 120 | 121 | - (void)keyboardAction:(NSNotification *)notification complete:(void(^)(CGFloat hKeyB))complete { 122 | NSDictionary *userInfo = [notification userInfo]; 123 | NSValue *animationCurveObject =[userInfo valueForKey:UIKeyboardAnimationCurveUserInfoKey]; 124 | 125 | NSValue *animationDurationObject =[userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey]; 126 | 127 | NSValue *keyboardEndRectObject =[userInfo valueForKey:UIKeyboardFrameEndUserInfoKey]; 128 | 129 | NSUInteger animationCurve = 0; 130 | double animationDuration = 0.0f; 131 | CGRect keyboardEndRect = CGRectMake(0,0, 0, 0); 132 | [animationCurveObject getValue:&animationCurve]; 133 | [animationDurationObject getValue:&animationDuration]; 134 | [keyboardEndRectObject getValue:&keyboardEndRect]; 135 | 136 | CGFloat hKeyB = 0; 137 | hKeyB = keyboardEndRect.size.height; 138 | 139 | [UIView beginAnimations:@"keyboardAction" context:NULL]; 140 | [UIView setAnimationDuration:animationDuration]; 141 | [UIView setAnimationCurve:(UIViewAnimationCurve)animationCurve]; 142 | complete(hKeyB); 143 | [UIView commitAnimations]; 144 | } 145 | 146 | #pragma mark Responding to keyboard events 147 | 148 | - (void)keyboardWillShow:(NSNotification *)notification { 149 | [self keyboardAction:notification complete:^(CGFloat hKeyB) { 150 | [self p_setOperateView:self.frame.size.height - hKeyB]; 151 | }]; 152 | } 153 | 154 | - (void)keyboardWillHide:(NSNotification *)notification { 155 | [self keyboardAction:notification complete:^(CGFloat hKeyB) { 156 | [self p_setOperateView:self.frame.size.height]; 157 | }]; 158 | } 159 | 160 | #pragma mark - Get 161 | 162 | - (UIControl *)blackOverlay { 163 | if (_blackOverlay == nil) { 164 | _blackOverlay = [[UIControl alloc] initWithFrame:self.bounds]; 165 | _blackOverlay.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; 166 | UIColor *maskColor = [UIColor colorWithWhite:0.0 alpha:0.3]; 167 | _blackOverlay.backgroundColor = maskColor; 168 | } 169 | 170 | return _blackOverlay; 171 | } 172 | 173 | @end 174 | -------------------------------------------------------------------------------- /QHDanumuDemo/QHDanmuSend/QHDanmuSendView.m: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuSendView.m 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/7/8. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import "QHDanmuSendView.h" 10 | 11 | #import "QHDanmuOperateView.h" 12 | 13 | #define VIEW_OPERATE_HEIGHT 44 14 | 15 | @interface QHDanmuSendView () 16 | 17 | @property (nonatomic, strong) QHDanmuOperateView *danmuOperateV; 18 | @property (nonatomic, strong) UIControl *blackOverlay; 19 | 20 | @end 21 | 22 | @implementation QHDanmuSendView 23 | 24 | - (void)dealloc { 25 | [self removeKeyboardNotificationCenter]; 26 | self.danmuOperateV = nil; 27 | self.blackOverlay = nil; 28 | } 29 | 30 | - (instancetype)initWithFrame:(CGRect)frame 31 | { 32 | self = [super initWithFrame:frame]; 33 | if (self) { 34 | // Do any additional setup after loading the view. 35 | self.backgroundColor = [UIColor clearColor]; 36 | self.userInteractionEnabled = YES; 37 | 38 | [self addSubview:self.blackOverlay]; 39 | 40 | self.danmuOperateV = [[QHDanmuOperateView alloc] initWithFrame:CGRectMake(0, self.frame.size.height - VIEW_OPERATE_HEIGHT, self.frame.size.width, VIEW_OPERATE_HEIGHT)]; 41 | self.danmuOperateV.deleagte = self; 42 | self.danmuOperateV.editContentTF.delegate = self; 43 | [self addSubview:self.danmuOperateV]; 44 | 45 | [self addKeyboardNotificationCenter]; 46 | 47 | self.alpha = 0; 48 | } 49 | return self; 50 | } 51 | 52 | #pragma mark - Private 53 | 54 | //重设界面布局 55 | - (void)p_setOperateView:(CGFloat)h { 56 | CGFloat top = h - CGRectGetHeight(self.danmuOperateV.frame); 57 | CGRect frame = self.danmuOperateV.frame; 58 | frame.origin.y = top; 59 | self.danmuOperateV.frame = frame; 60 | } 61 | 62 | #pragma mark - UITextFieldDelegate 63 | 64 | - (BOOL)textFieldShouldReturn:(UITextField *)textField { 65 | if ([self.deleagte respondsToSelector:@selector(sendDanmu:info:)]) { 66 | [self.deleagte sendDanmu:self info:textField.text]; 67 | } 68 | [self backAction]; 69 | return NO; 70 | } 71 | 72 | #pragma mark - QHDanmuOperateViewDelegate 73 | 74 | - (void)closeDanmu:(UIButton *)btn { 75 | [self backAction]; 76 | } 77 | 78 | #pragma mark - Action 79 | 80 | - (void)showAction:(UIView *)superView { 81 | self.alpha = 1; 82 | CGRect frame = self.frame; 83 | frame.origin.y = superView.frame.size.height; 84 | self.frame = frame; 85 | [UIView animateWithDuration:0.6 animations:^{ 86 | CGRect frame = self.frame; 87 | frame.origin.y = superView.frame.size.height - self.frame.size.height; 88 | self.frame = frame; 89 | } completion:^(BOOL finished) { 90 | [self.danmuOperateV.editContentTF becomeFirstResponder]; 91 | }]; 92 | } 93 | 94 | - (void)backAction { 95 | [self.danmuOperateV.editContentTF resignFirstResponder]; 96 | [UIView animateWithDuration:0.6 animations:^{ 97 | CGRect frame = self.frame; 98 | frame.origin.y = self.superview.frame.size.height; 99 | self.frame = frame; 100 | } completion:^(BOOL finished) { 101 | if ([self.deleagte respondsToSelector:@selector(closeSendDanmu:)]) { 102 | [self.deleagte closeSendDanmu:self]; 103 | } 104 | [self removeFromSuperview]; 105 | }]; 106 | } 107 | 108 | #pragma mark keyboardaction 109 | 110 | - (void)addKeyboardNotificationCenter { 111 | // Do any additional setup after loading the view. 112 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil]; 113 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; 114 | } 115 | 116 | - (void)removeKeyboardNotificationCenter { 117 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil]; 118 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil]; 119 | } 120 | 121 | - (void)keyboardAction:(NSNotification *)notification complete:(void(^)(CGFloat hKeyB))complete { 122 | NSDictionary *userInfo = [notification userInfo]; 123 | NSValue *animationCurveObject =[userInfo valueForKey:UIKeyboardAnimationCurveUserInfoKey]; 124 | 125 | NSValue *animationDurationObject =[userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey]; 126 | 127 | NSValue *keyboardEndRectObject =[userInfo valueForKey:UIKeyboardFrameEndUserInfoKey]; 128 | 129 | NSUInteger animationCurve = 0; 130 | double animationDuration = 0.0f; 131 | CGRect keyboardEndRect = CGRectMake(0,0, 0, 0); 132 | [animationCurveObject getValue:&animationCurve]; 133 | [animationDurationObject getValue:&animationDuration]; 134 | [keyboardEndRectObject getValue:&keyboardEndRect]; 135 | 136 | CGFloat hKeyB = 0; 137 | hKeyB = keyboardEndRect.size.height; 138 | 139 | [UIView beginAnimations:@"keyboardAction" context:NULL]; 140 | [UIView setAnimationDuration:animationDuration]; 141 | [UIView setAnimationCurve:(UIViewAnimationCurve)animationCurve]; 142 | complete(hKeyB); 143 | [UIView commitAnimations]; 144 | } 145 | 146 | #pragma mark Responding to keyboard events 147 | 148 | - (void)keyboardWillShow:(NSNotification *)notification { 149 | [self keyboardAction:notification complete:^(CGFloat hKeyB) { 150 | [self p_setOperateView:self.frame.size.height - hKeyB]; 151 | }]; 152 | } 153 | 154 | - (void)keyboardWillHide:(NSNotification *)notification { 155 | [self keyboardAction:notification complete:^(CGFloat hKeyB) { 156 | [self p_setOperateView:self.frame.size.height]; 157 | }]; 158 | } 159 | 160 | #pragma mark - Get 161 | 162 | - (UIControl *)blackOverlay { 163 | if (_blackOverlay == nil) { 164 | _blackOverlay = [[UIControl alloc] initWithFrame:self.bounds]; 165 | _blackOverlay.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; 166 | UIColor *maskColor = [UIColor colorWithWhite:0.0 alpha:0.3]; 167 | _blackOverlay.backgroundColor = maskColor; 168 | } 169 | 170 | return _blackOverlay; 171 | } 172 | 173 | @end 174 | -------------------------------------------------------------------------------- /QHDanumuDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/6/28. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | #import "QHDanmuManager.h" 12 | #import "QHDanmuSendView.h" 13 | 14 | #import "NSTimer+EOCBlocksSupport.h" 15 | 16 | @interface ViewController () 17 | 18 | @property (weak, nonatomic) IBOutlet UIView *screenV; 19 | @property (weak, nonatomic) IBOutlet UILabel *playTimeLabel; 20 | @property (nonatomic, strong) NSTimer *timer; 21 | @property (nonatomic, assign) NSTimeInterval countTime; 22 | 23 | @property (nonatomic, strong) QHDanmuManager *danmuManager; 24 | @property (nonatomic, strong) QHDanmuSendView *danmuSendV; 25 | 26 | @property (nonatomic) BOOL bPlaying; 27 | 28 | @end 29 | 30 | @implementation ViewController 31 | 32 | - (void)dealloc { 33 | self.danmuManager = nil; 34 | self.danmuSendV = nil; 35 | [self p_destoryTimer]; 36 | } 37 | 38 | - (void)viewDidLoad { 39 | [super viewDidLoad]; 40 | // Do any additional setup after loading the view, typically from a nib. 41 | 42 | self.bPlaying = NO; 43 | NSString *path = [[NSBundle mainBundle] bundlePath]; 44 | path = [[path stringByAppendingPathComponent:@"QHDanmuSource"] stringByAppendingPathExtension:@"plist"]; 45 | NSArray *tempInfos = [NSArray arrayWithContentsOfFile:path]; 46 | 47 | NSArray *infos = [tempInfos sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { 48 | CGFloat v1 = [[obj1 objectForKey:kDanmuTimeKey] floatValue]; 49 | CGFloat v2 = [[obj2 objectForKey:kDanmuTimeKey] floatValue]; 50 | 51 | NSComparisonResult result = v1 <= v2 ? NSOrderedAscending : NSOrderedDescending; 52 | 53 | return result; 54 | }]; 55 | tempInfos = nil; 56 | // NSLog(@"%@", infos); 57 | 58 | self.danmuManager = [[QHDanmuManager alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, _screenV.bounds.size.height) data:infos inView:_screenV durationTime:1]; 59 | 60 | self.countTime = -1; 61 | [self.danmuManager initStart]; 62 | } 63 | 64 | //iOS8旋转动作的具体执行 65 | - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { 66 | [coordinator animateAlongsideTransition: ^(id context) { 67 | if ([QHDanmuUtil isOrientationLandscape]) { 68 | [self p_prepareFullScreen]; 69 | } 70 | else { 71 | [self p_prepareSmallScreen]; 72 | } 73 | } completion: ^(id context) { 74 | if (self.bPlaying) 75 | [self start:nil]; 76 | }]; 77 | 78 | [super viewWillTransitionToSize: size withTransitionCoordinator: coordinator]; 79 | } 80 | 81 | //iOS7旋转动作的具体执行 82 | - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { 83 | if (toInterfaceOrientation == UIDeviceOrientationLandscapeRight || toInterfaceOrientation == UIDeviceOrientationLandscapeLeft) { 84 | [self p_prepareFullScreen]; 85 | } 86 | else { 87 | [self p_prepareSmallScreen]; 88 | } 89 | if (self.bPlaying) 90 | [self start:nil]; 91 | } 92 | 93 | - (void)didReceiveMemoryWarning { 94 | [super didReceiveMemoryWarning]; 95 | // Dispose of any resources that can be recreated. 96 | } 97 | 98 | #pragma mark - Private 99 | 100 | - (void)p_destoryTimer { 101 | if (_timer != nil) { 102 | [_timer invalidate]; 103 | _timer = nil; 104 | } 105 | } 106 | 107 | // 切换成全屏的准备工作 108 | - (void)p_prepareFullScreen { 109 | [self p_prepare]; 110 | } 111 | 112 | // 切换成小屏的准备工作 113 | - (void)p_prepareSmallScreen { 114 | [self p_prepare]; 115 | } 116 | 117 | //由于这里大小屏无需区分,真正应用场景肯定是要区分的操作的 118 | - (void)p_prepare { 119 | [self.danmuSendV backAction]; 120 | [self p_destoryTimer]; 121 | BOOL bPlaying = self.bPlaying; 122 | [self stop:nil]; 123 | self.bPlaying = bPlaying; 124 | [self.danmuManager resetDanmuWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, _screenV.bounds.size.height)]; 125 | } 126 | 127 | #pragma mark - QHDanmuSendViewDelegate 128 | 129 | - (void)sendDanmu:(QHDanmuSendView *)danmuSendV info:(NSString *)info { 130 | NSDate *now = [NSDate new]; 131 | double t = ((double)now.timeIntervalSince1970); 132 | t = ((int)t)%1000; 133 | CGFloat nowTime = self.countTime + t*0.0001; 134 | [self.danmuManager insertDanmu:@{kDanmuContentKey:info, kDanmuTimeKey:@(nowTime), kDanmuOptionalKey:@"df"}]; 135 | 136 | if (self.bPlaying) 137 | [self resume:nil]; 138 | } 139 | 140 | - (void)closeSendDanmu:(QHDanmuSendView *)danmuSendV { 141 | if (self.bPlaying) 142 | [self resume:nil]; 143 | } 144 | 145 | #pragma mark - Action 146 | 147 | - (IBAction)start:(id)sender { 148 | [self.danmuManager initStart]; 149 | self.bPlaying = YES; 150 | 151 | if ([_timer isValid]) { 152 | return; 153 | } 154 | if (_timer == nil) { 155 | __weak typeof(self) weakSelf = self; 156 | self.timer = [NSTimer eoc_scheduledTimerWithTimeInterval:1 block:^{ 157 | ViewController *strogSelf = weakSelf; 158 | [strogSelf progressVideo]; 159 | } repeats:YES]; 160 | } 161 | } 162 | 163 | - (IBAction)stop:(id)sender { 164 | self.bPlaying = NO; 165 | [self.danmuManager stop]; 166 | } 167 | 168 | - (IBAction)pause:(id)sender { 169 | [self p_destoryTimer]; 170 | [self.danmuManager pause]; 171 | } 172 | 173 | - (IBAction)resume:(id)sender { 174 | [self.danmuManager resume:_countTime]; 175 | 176 | [self start:nil]; 177 | } 178 | 179 | - (IBAction)restart:(id)sender { 180 | self.countTime = -1; 181 | [self.danmuManager restart]; 182 | [self p_destoryTimer]; 183 | 184 | [self start:nil]; 185 | } 186 | 187 | - (IBAction)send:(id)sender { 188 | if (self.danmuSendV != nil) { 189 | self.danmuSendV = nil; 190 | } 191 | self.danmuSendV = [[QHDanmuSendView alloc] initWithFrame:self.view.bounds]; 192 | [self.view addSubview:self.danmuSendV]; 193 | self.danmuSendV.deleagte = self; 194 | [self.danmuSendV showAction:self.view]; 195 | 196 | if (self.bPlaying) 197 | [self pause:nil]; 198 | } 199 | 200 | - (void)progressVideo { 201 | self.countTime++; 202 | [_danmuManager rollDanmu:_countTime]; 203 | } 204 | 205 | - (IBAction)clickScreenView:(id)sender { 206 | #ifdef DEBUG 207 | NSLog(@"hello world"); 208 | #endif 209 | } 210 | 211 | #pragma mark - Get 212 | 213 | - (void)setCountTime:(NSTimeInterval)countTime { 214 | _countTime = countTime; 215 | self.playTimeLabel.text = [NSString stringWithFormat:@"%f", _countTime]; 216 | } 217 | 218 | @end 219 | -------------------------------------------------------------------------------- /QHDanmuControl/QHDanmu/QHDanmuLabel.m: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuLabel.m 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/7/2. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import "QHDanmuLabel.h" 10 | 11 | static NSString * const DANMU_FONT_SIZE_BIG = @"l"; 12 | static NSString * const DANMU_FONT_SIZE_MIDDLE = @"m"; 13 | static NSString * const DANMU_FONT_SIZE_SMALL = @"s"; 14 | 15 | static NSString * const DANMU_MOVE_MODE_ROLLING = @"l"; 16 | static NSString * const DANMU_MOVE_MODE_FADEOUT = @"f"; 17 | 18 | static NSString * const DANMU_POSITION_TOP = @"t"; 19 | static NSString * const DANMU_POSITION_MIDDLE = @"m"; 20 | static NSString * const DANMU_POSITION_BOTTOM = @"b"; 21 | 22 | #define ARC4RANDOM_MAX 0x100000000 23 | 24 | #define ROLL_ANIMATION_DURATION_TIME 5 25 | 26 | #define FADE_ANIMATION_DURATION_TIME 2 27 | #define ANIMATION_DELAY_TIME 3 28 | 29 | @interface QHDanmuLabel () 30 | 31 | @property (nonatomic, strong, readwrite) NSDictionary *info; 32 | @property (nonatomic, readwrite) DanmuState danmuState; 33 | @property (nonatomic, readwrite) CGFloat animationDuartion; 34 | @property (nonatomic, strong, readwrite) NSDictionary *dicOptional; 35 | 36 | @property (nonatomic, readwrite) CGFloat speed; 37 | @property (nonatomic, readwrite) CGFloat currentRightX; 38 | 39 | @property (nonatomic) NSUInteger nChannel; 40 | @property (nonatomic, weak) UIView *superView; 41 | 42 | @property (nonatomic) CGFloat originalX; 43 | 44 | @end 45 | 46 | @implementation QHDanmuLabel 47 | 48 | - (void)dealloc { 49 | _info = nil; 50 | _dicOptional = nil; 51 | } 52 | 53 | #pragma mark - Private 54 | 55 | - (void)p_initData { 56 | self.textColor = [UIColor blackColor]; 57 | // 弹幕颜色 58 | // id optional = [self.info objectForKey:kDanmuOptionalKey]; 59 | // if ([optional isKindOfClass:[NSString class]]) { 60 | // optional = [QHDanmuUtil defaultOptions]; 61 | // } 62 | id optional = [QHDanmuUtil randomOptions]; 63 | self.dicOptional = [NSDictionary dictionaryWithDictionary:optional]; 64 | 65 | int color = [[optional objectForKey:@"c"] intValue]; 66 | [self setTextColor:[QHDanmuUtil colorFromARGB: color]]; 67 | 68 | CGFloat fontsize = 15; 69 | if (self.isFontSizeBig) { 70 | fontsize = 19; 71 | }else if (self.isFontSizeMiddle) { 72 | fontsize = 17; 73 | } 74 | 75 | UIFont *font = [UIFont systemFontOfSize:fontsize]; 76 | self.font = font; 77 | 78 | NSString *content = [_info objectForKey:kDanmuContentKey]; 79 | self.text = content; 80 | } 81 | 82 | - (void)p_initFrame:(CGFloat)offsetX { 83 | if (self.isMoveModeFadeOut) { 84 | NSInteger plus = ((arc4random() % 2) + 1) == 1 ? 1 : -1; 85 | offsetX = floorf(((double)arc4random() / ARC4RANDOM_MAX) * 30.0f)*plus; 86 | NSString *content = [self.info objectForKey:kDanmuContentKey]; 87 | CGSize size = [QHDanmuUtil getSizeWithString:content withFont:self.font size:(CGSize){MAXFLOAT, CHANNEL_HEIGHT}]; 88 | CGRect frame = (CGRect){(CGPoint){0, 0}, size}; 89 | self.frame = frame; 90 | 91 | CGPoint center = _superView.center; 92 | center.x += offsetX; 93 | self.center = center; 94 | } 95 | else { 96 | NSString *content = [self.info objectForKey:kDanmuContentKey]; 97 | CGSize size = [QHDanmuUtil getSizeWithString:content withFont:self.font size:(CGSize){MAXFLOAT, CHANNEL_HEIGHT}]; 98 | CGRect frame = (CGRect){(CGPoint){_superView.frame.size.width + offsetX, 0}, size}; 99 | self.frame = frame; 100 | _originalX = frame.origin.x + frame.size.width; 101 | } 102 | } 103 | 104 | - (void)p_rollAnimation:(CGFloat)time delay:(NSTimeInterval)waitTime { 105 | dispatch_async(dispatch_get_main_queue(), ^{ 106 | self.danmuState = DanmuStateAnimationing; 107 | [UIView animateWithDuration:time delay:waitTime options:UIViewAnimationOptionCurveLinear animations:^{ 108 | CGRect frame = self.frame; 109 | frame.origin.x = -self.frame.size.width; 110 | self.frame = frame; 111 | } completion:^(BOOL finished) { 112 | if (finished) 113 | [self removeDanmu]; 114 | }]; 115 | }); 116 | } 117 | 118 | - (void)p_fadeAnimation:(CGFloat)time delay:(NSTimeInterval)disappearTime waitTime:(NSTimeInterval)waitTime { 119 | dispatch_async(dispatch_get_main_queue(), ^{ 120 | if (waitTime == 0) { 121 | self.alpha = 1; 122 | self.danmuState = DanmuStateAnimationing; 123 | [UIView animateWithDuration:time delay:disappearTime options:UIViewAnimationOptionCurveLinear animations:^{ 124 | self.alpha = 0.2; 125 | } completion:^(BOOL finished) { 126 | if (finished) 127 | [self removeDanmu]; 128 | }]; 129 | } 130 | else { 131 | self.alpha = 0; 132 | [UIView animateWithDuration:0 delay:waitTime options:UIViewAnimationOptionCurveLinear animations:^{ 133 | self.alpha = 1; 134 | } completion:^(BOOL finished) { 135 | self.danmuState = DanmuStateAnimationing; 136 | [UIView animateWithDuration:time delay:disappearTime options:UIViewAnimationOptionCurveLinear animations:^{ 137 | self.alpha = 0.2; 138 | } completion:^(BOOL finished) { 139 | if (finished) 140 | [self removeDanmu]; 141 | }]; 142 | }]; 143 | } 144 | }); 145 | } 146 | 147 | #pragma mark - Action 148 | 149 | + (instancetype)createWithInfo:(NSDictionary *)info inView:(UIView *)view { 150 | QHDanmuLabel *danmuLabel = [[QHDanmuLabel alloc] init]; 151 | 152 | danmuLabel.info = info; 153 | danmuLabel.superView = view; 154 | danmuLabel.nChannel = 0; 155 | 156 | [danmuLabel p_initData]; 157 | [danmuLabel p_initFrame:0]; 158 | 159 | return danmuLabel; 160 | } 161 | 162 | - (void)setDanmuChannel:(NSUInteger)channel offset:(CGFloat)xy { 163 | if (self.isMoveModeFadeOut) { 164 | self.danmuState = DanmuStateStop; 165 | self.nChannel = channel; 166 | CGRect frame = self.frame; 167 | frame.origin.y = CHANNEL_HEIGHT*_nChannel + xy; 168 | self.frame = frame; 169 | [_superView addSubview:self]; 170 | } 171 | else { 172 | self.danmuState = DanmuStateStop; 173 | self.nChannel = channel; 174 | CGRect frame = self.frame; 175 | frame.origin.x += xy; 176 | frame.origin.y = CHANNEL_HEIGHT*_nChannel; 177 | self.frame = frame; 178 | _originalX = frame.origin.x + frame.size.width; 179 | [_superView addSubview:self]; 180 | } 181 | } 182 | 183 | - (void)animationDanmuItem:(NSTimeInterval)waitTime { 184 | if (self.isMoveModeFadeOut) { 185 | [self p_fadeAnimation:FADE_ANIMATION_DURATION_TIME delay:ANIMATION_DELAY_TIME waitTime:waitTime]; 186 | } 187 | else { 188 | [self p_rollAnimation:self.animationDuartion delay:waitTime]; 189 | } 190 | } 191 | 192 | - (void)pause { 193 | dispatch_async(dispatch_get_main_queue(), ^{ 194 | if (self.isMoveModeFadeOut) { 195 | // self.danmuState = DanmuStateStop; 196 | // self.alpha = 1.0; 197 | // [self.layer removeAllAnimations]; 198 | } 199 | else { 200 | self.danmuState = DanmuStateStop; 201 | UIView *view = self; 202 | CALayer *layer = view.layer; 203 | CGRect rect = view.frame; 204 | if (layer.presentationLayer) { 205 | rect = ((CALayer *)layer.presentationLayer).frame; 206 | // rect.origin.x -= 1; 207 | } 208 | view.frame = rect; 209 | [view.layer removeAllAnimations]; 210 | } 211 | }); 212 | } 213 | 214 | - (void)resume:(NSTimeInterval)nowTime { 215 | if (self.isMoveModeFadeOut) { 216 | // CGFloat startTime = self.startTime; 217 | // CGFloat time = nowTime - startTime; 218 | // 219 | // CGFloat waitTime = self.startTime; 220 | // if (waitTime > nowTime) 221 | // waitTime = waitTime - nowTime; 222 | // else 223 | // waitTime = 0; 224 | // 225 | // if (waitTime > 0) { 226 | // [self p_fadeAnimation:self.animationDuartion delay:ANIMATION_DELAY_TIME waitTime:waitTime]; 227 | // } 228 | // else { 229 | // [self p_fadeAnimation:time delay:ANIMATION_DELAY_TIME waitTime:0]; 230 | // } 231 | if (self.danmuState == DanmuStateStop) 232 | [self p_fadeAnimation:FADE_ANIMATION_DURATION_TIME delay:ANIMATION_DELAY_TIME waitTime:0]; 233 | } 234 | else { 235 | CGFloat waitTime = self.startTime; 236 | if (waitTime > nowTime) 237 | waitTime = waitTime - nowTime; 238 | else 239 | waitTime = 0; 240 | 241 | CGFloat time = (self.frame.origin.x + self.frame.size.width)/self.speed; 242 | [self p_rollAnimation:time delay:waitTime]; 243 | } 244 | } 245 | 246 | - (void)removeDanmu { 247 | dispatch_async(dispatch_get_main_queue(), ^{ 248 | if (self.isMoveModeFadeOut) { 249 | self.danmuState = DanmuStateFinish; 250 | [self.layer removeAllAnimations]; 251 | [self removeFromSuperview]; 252 | } 253 | else { 254 | self.danmuState = DanmuStateFinish; 255 | [self.layer removeAllAnimations]; 256 | [self removeFromSuperview]; 257 | } 258 | }); 259 | } 260 | 261 | #pragma mark - Get 262 | 263 | - (CGFloat)speed { 264 | _speed = _originalX/self.animationDuartion; 265 | return _speed; 266 | } 267 | 268 | - (CGFloat)animationDuartion { 269 | if (self.isMoveModeFadeOut) { 270 | //如果是竖屏 271 | _animationDuartion = FADE_ANIMATION_DURATION_TIME + ANIMATION_DELAY_TIME; 272 | //如果是横屏,根据不同尺寸,可能有不同的总时间 273 | } 274 | else { 275 | _animationDuartion = ROLL_ANIMATION_DURATION_TIME; 276 | } 277 | return _animationDuartion; 278 | } 279 | 280 | - (CGFloat)currentRightX { 281 | switch (self.danmuState) { 282 | case DanmuStateStop: { 283 | _currentRightX = CGRectGetMaxX(self.frame) - _superView.frame.size.width; 284 | break; 285 | } 286 | case DanmuStateAnimationing: { 287 | CALayer *layer = self.layer; 288 | _currentRightX = _originalX; 289 | if (layer.presentationLayer) 290 | _currentRightX = ((CALayer *)layer.presentationLayer).frame.origin.x + self.frame.size.width; 291 | _currentRightX -= _superView.frame.size.width; 292 | break; 293 | } 294 | case DanmuStateFinish: { 295 | _currentRightX = -_superView.frame.size.width; 296 | break; 297 | } 298 | default: { 299 | break; 300 | } 301 | } 302 | return _currentRightX; 303 | } 304 | 305 | #pragma mark - 306 | 307 | - (CGFloat)startTime { 308 | return [[self.info valueForKey:kDanmuTimeKey] floatValue]; 309 | } 310 | 311 | - (NSString *)fontSize { 312 | return [self.dicOptional valueForKey: @"s"]; 313 | } 314 | 315 | - (NSString *)moveMode { 316 | return [self.dicOptional valueForKey: @"m"]; 317 | } 318 | 319 | - (NSString *)position { 320 | return [self.dicOptional valueForKey: @"p"]; 321 | } 322 | 323 | - (BOOL)isFontSizeBig { 324 | return [[self fontSize] isEqualToString: DANMU_FONT_SIZE_BIG]; 325 | } 326 | 327 | - (BOOL)isFontSizeMiddle { 328 | return [[self fontSize] isEqualToString: DANMU_FONT_SIZE_MIDDLE]; 329 | } 330 | 331 | - (BOOL)isFontSizeSmall { 332 | return [[self fontSize] isEqualToString: DANMU_FONT_SIZE_SMALL]; 333 | } 334 | 335 | - (BOOL)isMoveModeRolling { 336 | return [[self moveMode] isEqualToString: DANMU_MOVE_MODE_ROLLING]; 337 | } 338 | 339 | - (BOOL)isMoveModeFadeOut { 340 | return [[self moveMode] isEqualToString: DANMU_MOVE_MODE_FADEOUT]; 341 | } 342 | 343 | - (BOOL)isPositionTop { 344 | return [[self position] isEqualToString: DANMU_POSITION_TOP]; 345 | } 346 | 347 | - (BOOL)isPositionMiddle { 348 | return [[self position] isEqualToString: DANMU_POSITION_MIDDLE]; 349 | } 350 | 351 | - (BOOL)isPositionBottom { 352 | return [[self position] isEqualToString: DANMU_POSITION_BOTTOM]; 353 | } 354 | 355 | @end 356 | -------------------------------------------------------------------------------- /QHDanumuDemo/QHDanmu/QHDanmuLabel.m: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuLabel.m 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/7/2. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import "QHDanmuLabel.h" 10 | 11 | static NSString * const DANMU_FONT_SIZE_BIG = @"l"; 12 | static NSString * const DANMU_FONT_SIZE_MIDDLE = @"m"; 13 | static NSString * const DANMU_FONT_SIZE_SMALL = @"s"; 14 | 15 | static NSString * const DANMU_MOVE_MODE_ROLLING = @"l"; 16 | static NSString * const DANMU_MOVE_MODE_FADEOUT = @"f"; 17 | 18 | static NSString * const DANMU_POSITION_TOP = @"t"; 19 | static NSString * const DANMU_POSITION_MIDDLE = @"m"; 20 | static NSString * const DANMU_POSITION_BOTTOM = @"b"; 21 | 22 | #define ARC4RANDOM_MAX 0x100000000 23 | 24 | #define ROLL_ANIMATION_DURATION_TIME 5 25 | 26 | #define FADE_ANIMATION_DURATION_TIME 2 27 | #define ANIMATION_DELAY_TIME 3 28 | 29 | @interface QHDanmuLabel () 30 | 31 | @property (nonatomic, strong, readwrite) NSDictionary *info; 32 | @property (nonatomic, readwrite) DanmuState danmuState; 33 | @property (nonatomic, readwrite) CGFloat animationDuartion; 34 | @property (nonatomic, strong, readwrite) NSDictionary *dicOptional; 35 | 36 | @property (nonatomic, readwrite) CGFloat speed; 37 | @property (nonatomic, readwrite) CGFloat currentRightX; 38 | 39 | @property (nonatomic) NSUInteger nChannel; 40 | @property (nonatomic, weak) UIView *superView; 41 | 42 | @property (nonatomic) CGFloat originalX; 43 | 44 | @end 45 | 46 | @implementation QHDanmuLabel 47 | 48 | - (void)dealloc { 49 | _info = nil; 50 | _dicOptional = nil; 51 | } 52 | 53 | #pragma mark - Private 54 | 55 | - (void)p_initData { 56 | self.textColor = [UIColor blackColor]; 57 | // 弹幕颜色 58 | // id optional = [self.info objectForKey:kDanmuOptionalKey]; 59 | // if ([optional isKindOfClass:[NSString class]]) { 60 | // optional = [QHDanmuUtil defaultOptions]; 61 | // } 62 | id optional = [QHDanmuUtil randomOptions]; 63 | self.dicOptional = [NSDictionary dictionaryWithDictionary:optional]; 64 | 65 | int color = [[optional objectForKey:@"c"] intValue]; 66 | [self setTextColor:[QHDanmuUtil colorFromARGB: color]]; 67 | 68 | CGFloat fontsize = 15; 69 | if (self.isFontSizeBig) { 70 | fontsize = 19; 71 | }else if (self.isFontSizeMiddle) { 72 | fontsize = 17; 73 | } 74 | 75 | UIFont *font = [UIFont systemFontOfSize:fontsize]; 76 | self.font = font; 77 | 78 | NSString *content = [_info objectForKey:kDanmuContentKey]; 79 | self.text = content; 80 | } 81 | 82 | - (void)p_initFrame:(CGFloat)offsetX { 83 | if (self.isMoveModeFadeOut) { 84 | NSInteger plus = ((arc4random() % 2) + 1) == 1 ? 1 : -1; 85 | offsetX = floorf(((double)arc4random() / ARC4RANDOM_MAX) * 30.0f)*plus; 86 | NSString *content = [self.info objectForKey:kDanmuContentKey]; 87 | CGSize size = [QHDanmuUtil getSizeWithString:content withFont:self.font size:(CGSize){MAXFLOAT, CHANNEL_HEIGHT}]; 88 | CGRect frame = (CGRect){(CGPoint){0, 0}, size}; 89 | self.frame = frame; 90 | 91 | CGPoint center = _superView.center; 92 | center.x += offsetX; 93 | self.center = center; 94 | } 95 | else { 96 | NSString *content = [self.info objectForKey:kDanmuContentKey]; 97 | CGSize size = [QHDanmuUtil getSizeWithString:content withFont:self.font size:(CGSize){MAXFLOAT, CHANNEL_HEIGHT}]; 98 | CGRect frame = (CGRect){(CGPoint){_superView.frame.size.width + offsetX, 0}, size}; 99 | self.frame = frame; 100 | _originalX = frame.origin.x + frame.size.width; 101 | } 102 | } 103 | 104 | - (void)p_rollAnimation:(CGFloat)time delay:(NSTimeInterval)waitTime { 105 | dispatch_async(dispatch_get_main_queue(), ^{ 106 | self.danmuState = DanmuStateAnimationing; 107 | [UIView animateWithDuration:time delay:waitTime options:UIViewAnimationOptionCurveLinear animations:^{ 108 | CGRect frame = self.frame; 109 | frame.origin.x = -self.frame.size.width; 110 | self.frame = frame; 111 | } completion:^(BOOL finished) { 112 | if (finished) 113 | [self removeDanmu]; 114 | }]; 115 | }); 116 | } 117 | 118 | - (void)p_fadeAnimation:(CGFloat)time delay:(NSTimeInterval)disappearTime waitTime:(NSTimeInterval)waitTime { 119 | dispatch_async(dispatch_get_main_queue(), ^{ 120 | if (waitTime == 0) { 121 | self.alpha = 1; 122 | self.danmuState = DanmuStateAnimationing; 123 | [UIView animateWithDuration:time delay:disappearTime options:UIViewAnimationOptionCurveLinear animations:^{ 124 | self.alpha = 0.2; 125 | } completion:^(BOOL finished) { 126 | if (finished) 127 | [self removeDanmu]; 128 | }]; 129 | } 130 | else { 131 | self.alpha = 0; 132 | [UIView animateWithDuration:0 delay:waitTime options:UIViewAnimationOptionCurveLinear animations:^{ 133 | self.alpha = 1; 134 | } completion:^(BOOL finished) { 135 | self.danmuState = DanmuStateAnimationing; 136 | [UIView animateWithDuration:time delay:disappearTime options:UIViewAnimationOptionCurveLinear animations:^{ 137 | self.alpha = 0.2; 138 | } completion:^(BOOL finished) { 139 | if (finished) 140 | [self removeDanmu]; 141 | }]; 142 | }]; 143 | } 144 | }); 145 | } 146 | 147 | #pragma mark - Action 148 | 149 | + (instancetype)createWithInfo:(NSDictionary *)info inView:(UIView *)view { 150 | QHDanmuLabel *danmuLabel = [[QHDanmuLabel alloc] init]; 151 | 152 | danmuLabel.info = info; 153 | danmuLabel.superView = view; 154 | danmuLabel.nChannel = 0; 155 | 156 | [danmuLabel p_initData]; 157 | [danmuLabel p_initFrame:0]; 158 | 159 | return danmuLabel; 160 | } 161 | 162 | - (void)setDanmuChannel:(NSUInteger)channel offset:(CGFloat)xy { 163 | if (self.isMoveModeFadeOut) { 164 | self.danmuState = DanmuStateStop; 165 | self.nChannel = channel; 166 | CGRect frame = self.frame; 167 | frame.origin.y = CHANNEL_HEIGHT*_nChannel + xy; 168 | self.frame = frame; 169 | [_superView addSubview:self]; 170 | } 171 | else { 172 | self.danmuState = DanmuStateStop; 173 | self.nChannel = channel; 174 | CGRect frame = self.frame; 175 | frame.origin.x += xy; 176 | frame.origin.y = CHANNEL_HEIGHT*_nChannel; 177 | self.frame = frame; 178 | _originalX = frame.origin.x + frame.size.width; 179 | [_superView addSubview:self]; 180 | } 181 | } 182 | 183 | - (void)animationDanmuItem:(NSTimeInterval)waitTime { 184 | if (self.isMoveModeFadeOut) { 185 | [self p_fadeAnimation:FADE_ANIMATION_DURATION_TIME delay:ANIMATION_DELAY_TIME waitTime:waitTime]; 186 | } 187 | else { 188 | [self p_rollAnimation:self.animationDuartion delay:waitTime]; 189 | } 190 | } 191 | 192 | - (void)pause { 193 | dispatch_async(dispatch_get_main_queue(), ^{ 194 | if (self.isMoveModeFadeOut) { 195 | // self.danmuState = DanmuStateStop; 196 | // self.alpha = 1.0; 197 | // [self.layer removeAllAnimations]; 198 | } 199 | else { 200 | self.danmuState = DanmuStateStop; 201 | UIView *view = self; 202 | CALayer *layer = view.layer; 203 | CGRect rect = view.frame; 204 | if (layer.presentationLayer) { 205 | rect = ((CALayer *)layer.presentationLayer).frame; 206 | // rect.origin.x -= 1; 207 | } 208 | view.frame = rect; 209 | [view.layer removeAllAnimations]; 210 | } 211 | }); 212 | } 213 | 214 | - (void)resume:(NSTimeInterval)nowTime { 215 | if (self.isMoveModeFadeOut) { 216 | // CGFloat startTime = self.startTime; 217 | // CGFloat time = nowTime - startTime; 218 | // 219 | // CGFloat waitTime = self.startTime; 220 | // if (waitTime > nowTime) 221 | // waitTime = waitTime - nowTime; 222 | // else 223 | // waitTime = 0; 224 | // 225 | // if (waitTime > 0) { 226 | // [self p_fadeAnimation:self.animationDuartion delay:ANIMATION_DELAY_TIME waitTime:waitTime]; 227 | // } 228 | // else { 229 | // [self p_fadeAnimation:time delay:ANIMATION_DELAY_TIME waitTime:0]; 230 | // } 231 | if (self.danmuState == DanmuStateStop) 232 | [self p_fadeAnimation:FADE_ANIMATION_DURATION_TIME delay:ANIMATION_DELAY_TIME waitTime:0]; 233 | } 234 | else { 235 | CGFloat waitTime = self.startTime; 236 | if (waitTime > nowTime) 237 | waitTime = waitTime - nowTime; 238 | else 239 | waitTime = 0; 240 | 241 | CGFloat time = (self.frame.origin.x + self.frame.size.width)/self.speed; 242 | [self p_rollAnimation:time delay:waitTime]; 243 | } 244 | } 245 | 246 | - (void)removeDanmu { 247 | dispatch_async(dispatch_get_main_queue(), ^{ 248 | if (self.isMoveModeFadeOut) { 249 | self.danmuState = DanmuStateFinish; 250 | [self.layer removeAllAnimations]; 251 | [self removeFromSuperview]; 252 | } 253 | else { 254 | self.danmuState = DanmuStateFinish; 255 | [self.layer removeAllAnimations]; 256 | [self removeFromSuperview]; 257 | } 258 | }); 259 | } 260 | 261 | #pragma mark - Get 262 | 263 | - (CGFloat)speed { 264 | _speed = _originalX/self.animationDuartion; 265 | return _speed; 266 | } 267 | 268 | - (CGFloat)animationDuartion { 269 | if (self.isMoveModeFadeOut) { 270 | //如果是竖屏 271 | _animationDuartion = FADE_ANIMATION_DURATION_TIME + ANIMATION_DELAY_TIME; 272 | //如果是横屏,根据不同尺寸,可能有不同的总时间 273 | } 274 | else { 275 | _animationDuartion = ROLL_ANIMATION_DURATION_TIME; 276 | } 277 | return _animationDuartion; 278 | } 279 | 280 | - (CGFloat)currentRightX { 281 | switch (self.danmuState) { 282 | case DanmuStateStop: { 283 | _currentRightX = CGRectGetMaxX(self.frame) - _superView.frame.size.width; 284 | break; 285 | } 286 | case DanmuStateAnimationing: { 287 | CALayer *layer = self.layer; 288 | _currentRightX = _originalX; 289 | if (layer.presentationLayer) 290 | _currentRightX = ((CALayer *)layer.presentationLayer).frame.origin.x + self.frame.size.width; 291 | _currentRightX -= _superView.frame.size.width; 292 | break; 293 | } 294 | case DanmuStateFinish: { 295 | _currentRightX = -_superView.frame.size.width; 296 | break; 297 | } 298 | default: { 299 | break; 300 | } 301 | } 302 | return _currentRightX; 303 | } 304 | 305 | #pragma mark - 306 | 307 | - (CGFloat)startTime { 308 | return [[self.info valueForKey:kDanmuTimeKey] floatValue]; 309 | } 310 | 311 | - (NSString *)fontSize { 312 | return [self.dicOptional valueForKey: @"s"]; 313 | } 314 | 315 | - (NSString *)moveMode { 316 | return [self.dicOptional valueForKey: @"m"]; 317 | } 318 | 319 | - (NSString *)position { 320 | return [self.dicOptional valueForKey: @"p"]; 321 | } 322 | 323 | - (BOOL)isFontSizeBig { 324 | return [[self fontSize] isEqualToString: DANMU_FONT_SIZE_BIG]; 325 | } 326 | 327 | - (BOOL)isFontSizeMiddle { 328 | return [[self fontSize] isEqualToString: DANMU_FONT_SIZE_MIDDLE]; 329 | } 330 | 331 | - (BOOL)isFontSizeSmall { 332 | return [[self fontSize] isEqualToString: DANMU_FONT_SIZE_SMALL]; 333 | } 334 | 335 | - (BOOL)isMoveModeRolling { 336 | return [[self moveMode] isEqualToString: DANMU_MOVE_MODE_ROLLING]; 337 | } 338 | 339 | - (BOOL)isMoveModeFadeOut { 340 | return [[self moveMode] isEqualToString: DANMU_MOVE_MODE_FADEOUT]; 341 | } 342 | 343 | - (BOOL)isPositionTop { 344 | return [[self position] isEqualToString: DANMU_POSITION_TOP]; 345 | } 346 | 347 | - (BOOL)isPositionMiddle { 348 | return [[self position] isEqualToString: DANMU_POSITION_MIDDLE]; 349 | } 350 | 351 | - (BOOL)isPositionBottom { 352 | return [[self position] isEqualToString: DANMU_POSITION_BOTTOM]; 353 | } 354 | 355 | @end 356 | -------------------------------------------------------------------------------- /QHDanmuControl/QHDanmu/QHDanmuManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuManager.m 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/6/28. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import "QHDanmuManager.h" 10 | 11 | #import "QHDanmuLabel.h" 12 | #import "QHDanmuView.h" 13 | 14 | #define CHANNEL_WIDTH_MAX 120 15 | #define CHANNEL_SPACE 10 16 | 17 | struct DanmuPositionStruct { 18 | NSInteger start; 19 | NSInteger length; 20 | }; 21 | typedef struct DanmuPositionStruct DanmuPositionStruct; 22 | 23 | @interface QHDanmuManager () 24 | 25 | @property (nonatomic, strong, readwrite) UIView *danmuView; 26 | @property (nonatomic, readwrite) DanmuManagerState danmuManagerState; 27 | 28 | @property (nonatomic) CGRect frame; 29 | @property (nonatomic, strong) NSMutableArray *infos; 30 | @property (nonatomic, weak) UIView *superView; 31 | @property (nonatomic) NSUInteger durationTime;//添加弹幕的间隔时间 32 | @property (nonatomic) NSUInteger currentIndex; 33 | 34 | @property (nonatomic, strong) NSMutableArray *arRollChannelInfo; 35 | @property (nonatomic, strong) NSMutableArray *arFadeChannelInfo; 36 | @property (nonatomic) NSUInteger countChannel; 37 | 38 | @property (nonatomic) DanmuPositionStruct upPosition; 39 | @property (nonatomic) DanmuPositionStruct middlePosition; 40 | @property (nonatomic) DanmuPositionStruct downPosition; 41 | 42 | @property (nonatomic) DanmuPositionStruct upFadeOnePosition; 43 | @property (nonatomic) DanmuPositionStruct middleFadeOnePosition; 44 | @property (nonatomic) DanmuPositionStruct downFadeOnePosition; 45 | 46 | @property (nonatomic) DanmuPositionStruct upFadeTwoPosition; 47 | @property (nonatomic) DanmuPositionStruct middleFadeTwoPosition; 48 | @property (nonatomic) DanmuPositionStruct downFadeTwoPosition; 49 | 50 | @property (nonatomic) dispatch_queue_t danmuQueue; 51 | 52 | @end 53 | 54 | @implementation QHDanmuManager 55 | 56 | - (void)dealloc { 57 | _infos = nil; 58 | _danmuView = nil; 59 | _arRollChannelInfo = nil; 60 | _arFadeChannelInfo = nil; 61 | } 62 | 63 | - (instancetype)initWithFrame:(CGRect)frame data:(NSArray *)infos inView:(UIView *)view durationTime:(NSUInteger)time { 64 | self = [super init]; 65 | if (self) { 66 | self.frame = frame; 67 | self.infos = [infos mutableCopy]; 68 | self.superView = view; 69 | self.durationTime = time; 70 | 71 | self.danmuQueue = dispatch_queue_create("com.danmu.queue", NULL); 72 | 73 | [self p_initInfo]; 74 | } 75 | return self; 76 | } 77 | 78 | #pragma mark - Private 79 | 80 | - (void)p_initInfo { 81 | _countChannel = self.frame.size.height/CHANNEL_HEIGHT; 82 | 83 | self.arRollChannelInfo = [NSMutableArray arrayWithCapacity:_countChannel]; 84 | self.arFadeChannelInfo = [NSMutableArray arrayWithCapacity:2]; 85 | 86 | NSUInteger sectionLines = nearbyintf((CGFloat)_countChannel / 3); 87 | NSUInteger firstLines = MAX(_countChannel - sectionLines*2, sectionLines); 88 | //滚动航道布局 89 | { 90 | //上中下,假设10,上:0-3,中:4-6,下:7-9 91 | _upPosition = (DanmuPositionStruct){0, firstLines}; 92 | _middlePosition = (DanmuPositionStruct){_upPosition.length, sectionLines}; 93 | _downPosition = (DanmuPositionStruct){_middlePosition.start + _middlePosition.length, _countChannel - _middlePosition.start - _middlePosition.length}; 94 | //上中下,假设10,上:0-9,中:4-9,下:7-9 95 | _upPosition = (DanmuPositionStruct){0, _countChannel}; 96 | _middlePosition = (DanmuPositionStruct){firstLines, _upPosition.length - firstLines}; 97 | _downPosition = (DanmuPositionStruct){_middlePosition.start + sectionLines, _upPosition.length - firstLines - sectionLines}; 98 | } 99 | //浮现航道布局,这里选择的是上面滚动航道布局,所以不一定是现在这样子 100 | { 101 | //第一层:上中下,假设10,上:0-9,中:4-9,下:7-9, 102 | _upFadeOnePosition = _upPosition; 103 | _middleFadeOnePosition = _middlePosition; 104 | _downFadeOnePosition = _downPosition; 105 | //由于上一层为10,第二层为9,上:0-8,中:4-8,下:7-8 106 | _upFadeTwoPosition = (DanmuPositionStruct){_upFadeOnePosition.start, _upFadeOnePosition.length - 1}; 107 | _middleFadeTwoPosition = (DanmuPositionStruct){_middleFadeOnePosition.start, _middleFadeOnePosition.length - 1}; 108 | _downFadeTwoPosition = (DanmuPositionStruct){_downFadeOnePosition.start, _downFadeOnePosition.length - 1}; 109 | } 110 | 111 | _danmuManagerState = DanmuManagerStateWait; 112 | } 113 | 114 | - (void)p_initData { 115 | [self.arRollChannelInfo removeAllObjects]; 116 | 117 | for (int i = 0; i < _countChannel; i++) { 118 | [self.arRollChannelInfo addObject:[NSNumber numberWithInt:i]]; 119 | } 120 | 121 | [self.arFadeChannelInfo removeAllObjects]; 122 | 123 | NSMutableArray *ar1 = [NSMutableArray new]; 124 | for (int i = 0; i < _countChannel; i++) { 125 | [ar1 addObject:[NSNumber numberWithInt:i]]; 126 | } 127 | [self.arFadeChannelInfo addObject:ar1]; 128 | NSMutableArray *ar2 = [NSMutableArray new]; 129 | for (int i = 0; i < _countChannel - 1; i++) { 130 | [ar2 addObject:[NSNumber numberWithInt:i]]; 131 | } 132 | [self.arFadeChannelInfo addObject:ar2]; 133 | 134 | self.currentIndex = 0; 135 | 136 | [self.danmuView removeFromSuperview]; 137 | self.danmuView = nil; 138 | self.danmuView = [[QHDanmuView alloc] initWithFrame:_frame]; 139 | [self.superView addSubview:self.danmuView]; 140 | 141 | _danmuManagerState = DanmuManagerStateWait; 142 | } 143 | 144 | - (void)p_danmu:(NSTimeInterval)startTime { 145 | __weak typeof(self) weakSelf = self; 146 | 147 | __block NSMutableArray *danmuInfos = [NSMutableArray new]; 148 | __block NSUInteger index = 0; 149 | 150 | for (int i = (int)self.currentIndex; i < self.infos.count; i++) { 151 | NSDictionary *obj = [self.infos objectAtIndex:i]; 152 | CGFloat time = [[obj objectForKey:kDanmuTimeKey] floatValue]; 153 | if (time >= startTime && time < startTime + weakSelf.durationTime) { 154 | [danmuInfos addObject:obj]; 155 | } 156 | if (time >= startTime + weakSelf.durationTime) { 157 | index = i; 158 | break; 159 | } 160 | } 161 | 162 | if (danmuInfos.count > 0) { 163 | self.currentIndex = index; 164 | 165 | void(^func)(QHDanmuLabel *danmuLabel, NSUInteger idx, CGFloat offsetXY) = ^(QHDanmuLabel *danmuLabel, NSUInteger idx, CGFloat offsetXY){ 166 | if (idx != NSNotFound) { 167 | [danmuLabel setDanmuChannel:idx offset:offsetXY]; 168 | CGFloat time = [danmuLabel startTime]; 169 | time = time - startTime; 170 | [danmuLabel animationDanmuItem:time]; 171 | } 172 | }; 173 | 174 | [danmuInfos enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) { 175 | __block QHDanmuLabel *danmuLabel = [QHDanmuLabel createWithInfo:obj inView:weakSelf.danmuView]; 176 | if ([danmuLabel isMoveModeFadeOut]) { 177 | [weakSelf p_getFadeBestChannel:danmuLabel completion:^(NSUInteger idx, CGFloat offsetY) { 178 | func(danmuLabel, idx, offsetY); 179 | }]; 180 | } 181 | else { 182 | [weakSelf p_getRollBestChannel:danmuLabel completion:^(NSUInteger idx, CGFloat offsetX) { 183 | func(danmuLabel, idx, offsetX); 184 | }]; 185 | } 186 | }]; 187 | } 188 | } 189 | 190 | - (void)p_getRollBestChannel:(QHDanmuLabel *)newDanmuL completion:(void(^)(NSUInteger idx, CGFloat offsetX))completion { 191 | DanmuPositionStruct danmuPosition; 192 | if (newDanmuL.isPositionMiddle) { 193 | danmuPosition = _middlePosition; 194 | } 195 | else if (newDanmuL.isPositionBottom) { 196 | danmuPosition = _downPosition; 197 | } 198 | else { 199 | danmuPosition = _upPosition; 200 | } 201 | 202 | NSUInteger index = danmuPosition.start; 203 | BOOL bFind = NO; 204 | for (int i = (int)danmuPosition.start; i < danmuPosition.start + danmuPosition.length; i++) { 205 | id obj = [self.arRollChannelInfo objectAtIndex:i]; 206 | index = i; 207 | if ([obj isKindOfClass:[QHDanmuLabel class]]) { 208 | bFind = [self p_last:obj new:newDanmuL]; 209 | }else { 210 | bFind = YES; 211 | } 212 | 213 | if (bFind) 214 | break; 215 | } 216 | 217 | if (bFind) { 218 | id obj = [self.arRollChannelInfo objectAtIndex:index]; 219 | [self.arRollChannelInfo replaceObjectAtIndex:index withObject:newDanmuL]; 220 | if ([obj isKindOfClass:[QHDanmuLabel class]]) { 221 | CGFloat x = ((QHDanmuLabel *)obj).currentRightX; 222 | completion(index, x < 0 ? 0 : x); 223 | }else 224 | completion(index, 0); 225 | }else { 226 | if (index < danmuPosition.start + danmuPosition.length - 1) { 227 | index += 1; 228 | [self.arRollChannelInfo replaceObjectAtIndex:index withObject:newDanmuL]; 229 | completion(index, 0); 230 | } 231 | else { 232 | NSUInteger index = NSNotFound; 233 | index = [self p_allChannelWithPosition:danmuPosition new:newDanmuL]; 234 | if (index != NSNotFound) { 235 | QHDanmuLabel *obj = [self.arRollChannelInfo objectAtIndex:index]; 236 | [self.arRollChannelInfo replaceObjectAtIndex:index withObject:newDanmuL]; 237 | CGFloat x = obj.currentRightX; 238 | completion(index, x < CHANNEL_SPACE ? CHANNEL_SPACE : x); 239 | }else 240 | completion(NSNotFound, 0); 241 | } 242 | } 243 | } 244 | 245 | //选择完全不会碰撞的航道 246 | - (BOOL)p_last:(QHDanmuLabel *)lastDanmuL new:(QHDanmuLabel *)newDanmuL { 247 | CGFloat durationTime = newDanmuL.startTime - lastDanmuL.startTime; 248 | if (durationTime > newDanmuL.animationDuartion) { 249 | return YES; 250 | } 251 | CGFloat timeS = lastDanmuL.frame.size.width/lastDanmuL.speed; 252 | if (timeS >= durationTime) { 253 | return NO; 254 | } 255 | CGFloat timeE = newDanmuL.currentRightX/newDanmuL.speed; 256 | if (timeE <= durationTime) { 257 | return NO; 258 | } 259 | 260 | return YES; 261 | } 262 | 263 | //选择在不超出缓冲区的且缓冲区最长的航道 264 | - (NSUInteger)p_allChannelWithPosition:(DanmuPositionStruct)danmuPosition new:(QHDanmuLabel *)newDanmuL { 265 | CGFloat width = CHANNEL_WIDTH_MAX; 266 | NSUInteger index = NSNotFound; 267 | for (int i = (int)danmuPosition.start; i < danmuPosition.start + danmuPosition.length; i++) {id obj = [self.arRollChannelInfo objectAtIndex:i]; 268 | if ([obj isKindOfClass:[QHDanmuLabel class]]) { 269 | CGFloat rightX = ((QHDanmuLabel *)obj).currentRightX; 270 | if (rightX <= CHANNEL_WIDTH_MAX) { 271 | CGFloat xx = rightX; 272 | if (xx < width) { 273 | width = xx; 274 | index = i; 275 | } 276 | } 277 | } 278 | } 279 | 280 | return index; 281 | } 282 | 283 | - (void)p_getFadeBestChannel:(QHDanmuLabel *)newDanmuL completion:(void(^)(NSUInteger idx, CGFloat offsetY))completion { 284 | DanmuPositionStruct danmuOnePosition; 285 | DanmuPositionStruct danmuTwoPosition; 286 | if (newDanmuL.isPositionMiddle) { 287 | danmuOnePosition = _middleFadeOnePosition; 288 | danmuTwoPosition = _middleFadeTwoPosition; 289 | }else if (newDanmuL.isPositionBottom) { 290 | danmuOnePosition = _downFadeOnePosition; 291 | danmuTwoPosition = _downFadeTwoPosition; 292 | }else { 293 | danmuOnePosition = _upFadeOnePosition; 294 | danmuTwoPosition = _upFadeTwoPosition; 295 | } 296 | NSMutableArray *ar1 = [self.arFadeChannelInfo objectAtIndex:0]; 297 | NSMutableArray *ar2 = [self.arFadeChannelInfo objectAtIndex:1]; 298 | 299 | NSUInteger index = [self p_arDanmuLabel:ar1 position:danmuOnePosition new:newDanmuL]; 300 | 301 | if (index != NSNotFound) { 302 | [ar1 replaceObjectAtIndex:index withObject:newDanmuL]; 303 | completion(index, 0); 304 | }else { 305 | index = [self p_arDanmuLabel:ar2 position:danmuTwoPosition new:newDanmuL]; 306 | 307 | if (index != NSNotFound) { 308 | [ar2 replaceObjectAtIndex:index withObject:newDanmuL]; 309 | completion(index, newDanmuL.frame.size.height/2); 310 | }else { 311 | completion(NSNotFound, 0); 312 | } 313 | } 314 | } 315 | 316 | - (NSUInteger)p_arDanmuLabel:(NSMutableArray *)arDanmuLs position:(DanmuPositionStruct)danmuPosition new:(QHDanmuLabel *)newDanmuL { 317 | BOOL bFind = NO; 318 | NSUInteger index = danmuPosition.start; 319 | for (int i = (int)danmuPosition.start; i < danmuPosition.start + danmuPosition.length; i++) { 320 | id obj = [arDanmuLs objectAtIndex:i]; 321 | index = i; 322 | if ([obj isKindOfClass:[QHDanmuLabel class]]) { 323 | QHDanmuLabel *lastDanmuL = obj; 324 | CGFloat durationTime = newDanmuL.startTime - lastDanmuL.startTime; 325 | bFind = (durationTime > (newDanmuL.animationDuartion - 1)); 326 | }else { 327 | bFind = YES; 328 | } 329 | 330 | if (bFind) 331 | break; 332 | } 333 | if (!bFind) { 334 | if (index < danmuPosition.start + danmuPosition.length - 1) 335 | index += 1; 336 | else 337 | index = NSNotFound; 338 | } 339 | 340 | return index; 341 | } 342 | 343 | #pragma mark - Action 344 | 345 | - (void)initStart { 346 | if (_danmuManagerState == DanmuManagerStateWait || 347 | _danmuManagerState == DanmuManagerStateStop) { 348 | 349 | [self p_initData]; 350 | } 351 | } 352 | 353 | - (void)rollDanmu:(NSTimeInterval)startTime { 354 | if (_danmuManagerState == DanmuManagerStateStop) 355 | return; 356 | dispatch_sync(self.danmuQueue, ^{ 357 | if (_danmuManagerState != DanmuManagerStateAnimationing) 358 | _danmuManagerState = DanmuManagerStateAnimationing; 359 | 360 | if ((NSInteger)startTime % _durationTime == 0) { 361 | [self p_danmu:startTime]; 362 | } 363 | }); 364 | } 365 | 366 | - (void)stop { 367 | dispatch_sync(self.danmuQueue, ^{ 368 | _danmuManagerState = DanmuManagerStateStop; 369 | [self.arRollChannelInfo removeAllObjects]; 370 | [self.arFadeChannelInfo removeAllObjects]; 371 | dispatch_async(dispatch_get_main_queue(), ^{ 372 | [self.danmuView.subviews makeObjectsPerformSelector:@selector(removeDanmu)]; 373 | [self.danmuView removeFromSuperview]; 374 | }); 375 | }); 376 | } 377 | 378 | - (void)pause { 379 | if (_danmuManagerState != DanmuManagerStateAnimationing) 380 | return; 381 | dispatch_sync(self.danmuQueue, ^{ 382 | _danmuManagerState = DanmuManagerStatePause; 383 | [self.danmuView.subviews makeObjectsPerformSelector:@selector(pause)]; 384 | }); 385 | } 386 | 387 | - (void)resume:(NSTimeInterval)nowTime { 388 | if (_danmuManagerState != DanmuManagerStatePause) 389 | return; 390 | dispatch_sync(self.danmuQueue, ^{ 391 | _danmuManagerState = DanmuManagerStateAnimationing; 392 | for (id subview in self.danmuView.subviews) { 393 | if ([subview isKindOfClass:[QHDanmuLabel class]]) { 394 | [(QHDanmuLabel *)subview resume:nowTime]; 395 | } 396 | } 397 | }); 398 | } 399 | 400 | - (void)restart { 401 | [self p_initData]; 402 | dispatch_sync(self.danmuQueue, ^{ 403 | _danmuManagerState = DanmuManagerStateAnimationing; 404 | }); 405 | } 406 | 407 | - (void)insertDanmu:(NSDictionary *)info { 408 | dispatch_sync(self.danmuQueue, ^{ 409 | __block QHDanmuLabel *danmuLabel = [QHDanmuLabel createWithInfo:info inView:self.danmuView]; 410 | if ([danmuLabel isMoveModeFadeOut]) { 411 | [self p_getFadeBestChannel:danmuLabel completion:^(NSUInteger idx, CGFloat offsetY) { 412 | if (idx != NSNotFound) { 413 | [danmuLabel setDanmuChannel:idx offset:offsetY]; 414 | } 415 | }]; 416 | } 417 | else { 418 | [self p_getRollBestChannel:danmuLabel completion:^(NSUInteger idx, CGFloat offsetX) { 419 | if (idx != NSNotFound) { 420 | [danmuLabel setDanmuChannel:idx offset:offsetX]; 421 | } 422 | }]; 423 | } 424 | }); 425 | } 426 | 427 | - (void)resetDanmuWithFrame:(CGRect)frame data:(NSArray *)infos inView:(UIView *)view durationTime:(NSUInteger)time { 428 | self.frame = frame; 429 | if (infos != nil) 430 | self.infos = [infos mutableCopy]; 431 | self.superView = view; 432 | self.durationTime = time; 433 | 434 | [self p_initInfo]; 435 | } 436 | 437 | - (void)resetDanmuWithFrame:(CGRect)frame { 438 | self.frame = frame; 439 | [self p_initInfo]; 440 | } 441 | 442 | - (void)resetDanmuInfos:(NSArray *)infos { 443 | NSAssert(infos != nil, @"传入的弹幕信息不能为nil"); 444 | self.infos = nil; 445 | self.infos = [infos mutableCopy]; 446 | } 447 | 448 | @end 449 | -------------------------------------------------------------------------------- /QHDanumuDemo/QHDanmu/QHDanmuManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // QHDanmuManager.m 3 | // QHDanumuDemo 4 | // 5 | // Created by chen on 15/6/28. 6 | // Copyright (c) 2015年 chen. All rights reserved. 7 | // 8 | 9 | #import "QHDanmuManager.h" 10 | 11 | #import "QHDanmuLabel.h" 12 | #import "QHDanmuView.h" 13 | 14 | #define CHANNEL_WIDTH_MAX 120 15 | #define CHANNEL_SPACE 10 16 | 17 | struct DanmuPositionStruct { 18 | NSInteger start; 19 | NSInteger length; 20 | }; 21 | typedef struct DanmuPositionStruct DanmuPositionStruct; 22 | 23 | @interface QHDanmuManager () 24 | 25 | @property (nonatomic, strong, readwrite) UIView *danmuView; 26 | @property (nonatomic, readwrite) DanmuManagerState danmuManagerState; 27 | 28 | @property (nonatomic) CGRect frame; 29 | @property (nonatomic, strong) NSMutableArray *infos; 30 | @property (nonatomic, weak) UIView *superView; 31 | @property (nonatomic) NSUInteger durationTime;//添加弹幕的间隔时间 32 | @property (nonatomic) NSUInteger currentIndex; 33 | 34 | @property (nonatomic, strong) NSMutableArray *arRollChannelInfo; 35 | @property (nonatomic, strong) NSMutableArray *arFadeChannelInfo; 36 | @property (nonatomic) NSUInteger countChannel; 37 | 38 | @property (nonatomic) DanmuPositionStruct upPosition; 39 | @property (nonatomic) DanmuPositionStruct middlePosition; 40 | @property (nonatomic) DanmuPositionStruct downPosition; 41 | 42 | @property (nonatomic) DanmuPositionStruct upFadeOnePosition; 43 | @property (nonatomic) DanmuPositionStruct middleFadeOnePosition; 44 | @property (nonatomic) DanmuPositionStruct downFadeOnePosition; 45 | 46 | @property (nonatomic) DanmuPositionStruct upFadeTwoPosition; 47 | @property (nonatomic) DanmuPositionStruct middleFadeTwoPosition; 48 | @property (nonatomic) DanmuPositionStruct downFadeTwoPosition; 49 | 50 | @property (nonatomic) dispatch_queue_t danmuQueue; 51 | 52 | @end 53 | 54 | @implementation QHDanmuManager 55 | 56 | - (void)dealloc { 57 | _infos = nil; 58 | _danmuView = nil; 59 | _arRollChannelInfo = nil; 60 | _arFadeChannelInfo = nil; 61 | } 62 | 63 | - (instancetype)initWithFrame:(CGRect)frame data:(NSArray *)infos inView:(UIView *)view durationTime:(NSUInteger)time { 64 | self = [super init]; 65 | if (self) { 66 | self.frame = frame; 67 | self.infos = [infos mutableCopy]; 68 | self.superView = view; 69 | self.durationTime = time; 70 | 71 | self.danmuQueue = dispatch_queue_create("com.danmu.queue", NULL); 72 | 73 | [self p_initInfo]; 74 | } 75 | return self; 76 | } 77 | 78 | #pragma mark - Private 79 | 80 | - (void)p_initInfo { 81 | _countChannel = self.frame.size.height/CHANNEL_HEIGHT; 82 | 83 | self.arRollChannelInfo = [NSMutableArray arrayWithCapacity:_countChannel]; 84 | self.arFadeChannelInfo = [NSMutableArray arrayWithCapacity:2]; 85 | 86 | NSUInteger sectionLines = nearbyintf((CGFloat)_countChannel / 3); 87 | NSUInteger firstLines = MAX(_countChannel - sectionLines*2, sectionLines); 88 | //滚动航道布局 89 | { 90 | //上中下,假设10,上:0-3,中:4-6,下:7-9 91 | _upPosition = (DanmuPositionStruct){0, firstLines}; 92 | _middlePosition = (DanmuPositionStruct){_upPosition.length, sectionLines}; 93 | _downPosition = (DanmuPositionStruct){_middlePosition.start + _middlePosition.length, _countChannel - _middlePosition.start - _middlePosition.length}; 94 | //上中下,假设10,上:0-9,中:4-9,下:7-9 95 | _upPosition = (DanmuPositionStruct){0, _countChannel}; 96 | _middlePosition = (DanmuPositionStruct){firstLines, _upPosition.length - firstLines}; 97 | _downPosition = (DanmuPositionStruct){_middlePosition.start + sectionLines, _upPosition.length - firstLines - sectionLines}; 98 | } 99 | //浮现航道布局,这里选择的是上面滚动航道布局,所以不一定是现在这样子 100 | { 101 | //第一层:上中下,假设10,上:0-9,中:4-9,下:7-9, 102 | _upFadeOnePosition = _upPosition; 103 | _middleFadeOnePosition = _middlePosition; 104 | _downFadeOnePosition = _downPosition; 105 | //由于上一层为10,第二层为9,上:0-8,中:4-8,下:7-8 106 | _upFadeTwoPosition = (DanmuPositionStruct){_upFadeOnePosition.start, _upFadeOnePosition.length - 1}; 107 | _middleFadeTwoPosition = (DanmuPositionStruct){_middleFadeOnePosition.start, _middleFadeOnePosition.length - 1}; 108 | _downFadeTwoPosition = (DanmuPositionStruct){_downFadeOnePosition.start, _downFadeOnePosition.length - 1}; 109 | } 110 | 111 | _danmuManagerState = DanmuManagerStateWait; 112 | } 113 | 114 | - (void)p_initData { 115 | [self.arRollChannelInfo removeAllObjects]; 116 | 117 | for (int i = 0; i < _countChannel; i++) { 118 | [self.arRollChannelInfo addObject:[NSNumber numberWithInt:i]]; 119 | } 120 | 121 | [self.arFadeChannelInfo removeAllObjects]; 122 | 123 | NSMutableArray *ar1 = [NSMutableArray new]; 124 | for (int i = 0; i < _countChannel; i++) { 125 | [ar1 addObject:[NSNumber numberWithInt:i]]; 126 | } 127 | [self.arFadeChannelInfo addObject:ar1]; 128 | NSMutableArray *ar2 = [NSMutableArray new]; 129 | for (int i = 0; i < _countChannel - 1; i++) { 130 | [ar2 addObject:[NSNumber numberWithInt:i]]; 131 | } 132 | [self.arFadeChannelInfo addObject:ar2]; 133 | 134 | self.currentIndex = 0; 135 | 136 | [self.danmuView removeFromSuperview]; 137 | self.danmuView = nil; 138 | self.danmuView = [[QHDanmuView alloc] initWithFrame:_frame]; 139 | [self.superView addSubview:self.danmuView]; 140 | 141 | _danmuManagerState = DanmuManagerStateWait; 142 | } 143 | 144 | - (void)p_danmu:(NSTimeInterval)startTime { 145 | __weak typeof(self) weakSelf = self; 146 | 147 | __block NSMutableArray *danmuInfos = [NSMutableArray new]; 148 | __block NSUInteger index = 0; 149 | 150 | for (int i = (int)self.currentIndex; i < self.infos.count; i++) { 151 | NSDictionary *obj = [self.infos objectAtIndex:i]; 152 | CGFloat time = [[obj objectForKey:kDanmuTimeKey] floatValue]; 153 | if (time >= startTime && time < startTime + weakSelf.durationTime) { 154 | [danmuInfos addObject:obj]; 155 | } 156 | if (time >= startTime + weakSelf.durationTime) { 157 | index = i; 158 | break; 159 | } 160 | } 161 | 162 | if (danmuInfos.count > 0) { 163 | self.currentIndex = index; 164 | 165 | void(^func)(QHDanmuLabel *danmuLabel, NSUInteger idx, CGFloat offsetXY) = ^(QHDanmuLabel *danmuLabel, NSUInteger idx, CGFloat offsetXY){ 166 | if (idx != NSNotFound) { 167 | [danmuLabel setDanmuChannel:idx offset:offsetXY]; 168 | CGFloat time = [danmuLabel startTime]; 169 | time = time - startTime; 170 | [danmuLabel animationDanmuItem:time]; 171 | } 172 | }; 173 | 174 | [danmuInfos enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) { 175 | __block QHDanmuLabel *danmuLabel = [QHDanmuLabel createWithInfo:obj inView:weakSelf.danmuView]; 176 | if ([danmuLabel isMoveModeFadeOut]) { 177 | [weakSelf p_getFadeBestChannel:danmuLabel completion:^(NSUInteger idx, CGFloat offsetY) { 178 | func(danmuLabel, idx, offsetY); 179 | }]; 180 | } 181 | else { 182 | [weakSelf p_getRollBestChannel:danmuLabel completion:^(NSUInteger idx, CGFloat offsetX) { 183 | func(danmuLabel, idx, offsetX); 184 | }]; 185 | } 186 | }]; 187 | } 188 | } 189 | 190 | - (void)p_getRollBestChannel:(QHDanmuLabel *)newDanmuL completion:(void(^)(NSUInteger idx, CGFloat offsetX))completion { 191 | DanmuPositionStruct danmuPosition; 192 | if (newDanmuL.isPositionMiddle) { 193 | danmuPosition = _middlePosition; 194 | } 195 | else if (newDanmuL.isPositionBottom) { 196 | danmuPosition = _downPosition; 197 | } 198 | else { 199 | danmuPosition = _upPosition; 200 | } 201 | 202 | NSUInteger index = danmuPosition.start; 203 | BOOL bFind = NO; 204 | for (int i = (int)danmuPosition.start; i < danmuPosition.start + danmuPosition.length; i++) { 205 | id obj = [self.arRollChannelInfo objectAtIndex:i]; 206 | index = i; 207 | if ([obj isKindOfClass:[QHDanmuLabel class]]) { 208 | bFind = [self p_last:obj new:newDanmuL]; 209 | }else { 210 | bFind = YES; 211 | } 212 | 213 | if (bFind) 214 | break; 215 | } 216 | 217 | if (bFind) { 218 | id obj = [self.arRollChannelInfo objectAtIndex:index]; 219 | [self.arRollChannelInfo replaceObjectAtIndex:index withObject:newDanmuL]; 220 | if ([obj isKindOfClass:[QHDanmuLabel class]]) { 221 | CGFloat x = ((QHDanmuLabel *)obj).currentRightX; 222 | completion(index, x < 0 ? 0 : x); 223 | }else 224 | completion(index, 0); 225 | }else { 226 | if (index < danmuPosition.start + danmuPosition.length - 1) { 227 | index += 1; 228 | [self.arRollChannelInfo replaceObjectAtIndex:index withObject:newDanmuL]; 229 | completion(index, 0); 230 | } 231 | else { 232 | NSUInteger index = NSNotFound; 233 | index = [self p_allChannelWithPosition:danmuPosition new:newDanmuL]; 234 | if (index != NSNotFound) { 235 | QHDanmuLabel *obj = [self.arRollChannelInfo objectAtIndex:index]; 236 | [self.arRollChannelInfo replaceObjectAtIndex:index withObject:newDanmuL]; 237 | CGFloat x = obj.currentRightX; 238 | completion(index, x < CHANNEL_SPACE ? CHANNEL_SPACE : x); 239 | }else 240 | completion(NSNotFound, 0); 241 | } 242 | } 243 | } 244 | 245 | //选择完全不会碰撞的航道 246 | - (BOOL)p_last:(QHDanmuLabel *)lastDanmuL new:(QHDanmuLabel *)newDanmuL { 247 | CGFloat durationTime = newDanmuL.startTime - lastDanmuL.startTime; 248 | if (durationTime > newDanmuL.animationDuartion) { 249 | return YES; 250 | } 251 | CGFloat timeS = lastDanmuL.frame.size.width/lastDanmuL.speed; 252 | if (timeS >= durationTime) { 253 | return NO; 254 | } 255 | CGFloat timeE = newDanmuL.currentRightX/newDanmuL.speed; 256 | if (timeE <= durationTime) { 257 | return NO; 258 | } 259 | 260 | return YES; 261 | } 262 | 263 | //选择在不超出缓冲区的且缓冲区最长的航道 264 | - (NSUInteger)p_allChannelWithPosition:(DanmuPositionStruct)danmuPosition new:(QHDanmuLabel *)newDanmuL { 265 | CGFloat width = CHANNEL_WIDTH_MAX; 266 | NSUInteger index = NSNotFound; 267 | for (int i = (int)danmuPosition.start; i < danmuPosition.start + danmuPosition.length; i++) {id obj = [self.arRollChannelInfo objectAtIndex:i]; 268 | if ([obj isKindOfClass:[QHDanmuLabel class]]) { 269 | CGFloat rightX = ((QHDanmuLabel *)obj).currentRightX; 270 | if (rightX <= CHANNEL_WIDTH_MAX) { 271 | CGFloat xx = rightX; 272 | if (xx < width) { 273 | width = xx; 274 | index = i; 275 | } 276 | } 277 | } 278 | } 279 | 280 | return index; 281 | } 282 | 283 | - (void)p_getFadeBestChannel:(QHDanmuLabel *)newDanmuL completion:(void(^)(NSUInteger idx, CGFloat offsetY))completion { 284 | DanmuPositionStruct danmuOnePosition; 285 | DanmuPositionStruct danmuTwoPosition; 286 | if (newDanmuL.isPositionMiddle) { 287 | danmuOnePosition = _middleFadeOnePosition; 288 | danmuTwoPosition = _middleFadeTwoPosition; 289 | }else if (newDanmuL.isPositionBottom) { 290 | danmuOnePosition = _downFadeOnePosition; 291 | danmuTwoPosition = _downFadeTwoPosition; 292 | }else { 293 | danmuOnePosition = _upFadeOnePosition; 294 | danmuTwoPosition = _upFadeTwoPosition; 295 | } 296 | NSMutableArray *ar1 = [self.arFadeChannelInfo objectAtIndex:0]; 297 | NSMutableArray *ar2 = [self.arFadeChannelInfo objectAtIndex:1]; 298 | 299 | NSUInteger index = [self p_arDanmuLabel:ar1 position:danmuOnePosition new:newDanmuL]; 300 | 301 | if (index != NSNotFound) { 302 | [ar1 replaceObjectAtIndex:index withObject:newDanmuL]; 303 | completion(index, 0); 304 | }else { 305 | index = [self p_arDanmuLabel:ar2 position:danmuTwoPosition new:newDanmuL]; 306 | 307 | if (index != NSNotFound) { 308 | [ar2 replaceObjectAtIndex:index withObject:newDanmuL]; 309 | completion(index, newDanmuL.frame.size.height/2); 310 | }else { 311 | completion(NSNotFound, 0); 312 | } 313 | } 314 | } 315 | 316 | - (NSUInteger)p_arDanmuLabel:(NSMutableArray *)arDanmuLs position:(DanmuPositionStruct)danmuPosition new:(QHDanmuLabel *)newDanmuL { 317 | BOOL bFind = NO; 318 | NSUInteger index = danmuPosition.start; 319 | for (int i = (int)danmuPosition.start; i < danmuPosition.start + danmuPosition.length; i++) { 320 | id obj = [arDanmuLs objectAtIndex:i]; 321 | index = i; 322 | if ([obj isKindOfClass:[QHDanmuLabel class]]) { 323 | QHDanmuLabel *lastDanmuL = obj; 324 | CGFloat durationTime = newDanmuL.startTime - lastDanmuL.startTime; 325 | bFind = (durationTime > (newDanmuL.animationDuartion - 1)); 326 | }else { 327 | bFind = YES; 328 | } 329 | 330 | if (bFind) 331 | break; 332 | } 333 | if (!bFind) { 334 | if (index < danmuPosition.start + danmuPosition.length - 1) 335 | index += 1; 336 | else 337 | index = NSNotFound; 338 | } 339 | 340 | return index; 341 | } 342 | 343 | #pragma mark - Action 344 | 345 | - (void)initStart { 346 | if (_danmuManagerState == DanmuManagerStateWait || 347 | _danmuManagerState == DanmuManagerStateStop) { 348 | 349 | [self p_initData]; 350 | } 351 | } 352 | 353 | - (void)rollDanmu:(NSTimeInterval)startTime { 354 | if (_danmuManagerState == DanmuManagerStateStop) 355 | return; 356 | dispatch_sync(self.danmuQueue, ^{ 357 | if (_danmuManagerState != DanmuManagerStateAnimationing) 358 | _danmuManagerState = DanmuManagerStateAnimationing; 359 | 360 | if ((NSInteger)startTime % _durationTime == 0) { 361 | [self p_danmu:startTime]; 362 | } 363 | }); 364 | } 365 | 366 | - (void)stop { 367 | dispatch_sync(self.danmuQueue, ^{ 368 | _danmuManagerState = DanmuManagerStateStop; 369 | [self.arRollChannelInfo removeAllObjects]; 370 | [self.arFadeChannelInfo removeAllObjects]; 371 | dispatch_async(dispatch_get_main_queue(), ^{ 372 | [self.danmuView.subviews makeObjectsPerformSelector:@selector(removeDanmu)]; 373 | [self.danmuView removeFromSuperview]; 374 | }); 375 | }); 376 | } 377 | 378 | - (void)pause { 379 | if (_danmuManagerState != DanmuManagerStateAnimationing) 380 | return; 381 | dispatch_sync(self.danmuQueue, ^{ 382 | _danmuManagerState = DanmuManagerStatePause; 383 | [self.danmuView.subviews makeObjectsPerformSelector:@selector(pause)]; 384 | }); 385 | } 386 | 387 | - (void)resume:(NSTimeInterval)nowTime { 388 | if (_danmuManagerState != DanmuManagerStatePause) 389 | return; 390 | dispatch_sync(self.danmuQueue, ^{ 391 | _danmuManagerState = DanmuManagerStateAnimationing; 392 | for (id subview in self.danmuView.subviews) { 393 | if ([subview isKindOfClass:[QHDanmuLabel class]]) { 394 | [(QHDanmuLabel *)subview resume:nowTime]; 395 | } 396 | } 397 | }); 398 | } 399 | 400 | - (void)restart { 401 | [self p_initData]; 402 | dispatch_sync(self.danmuQueue, ^{ 403 | _danmuManagerState = DanmuManagerStateAnimationing; 404 | }); 405 | } 406 | 407 | - (void)insertDanmu:(NSDictionary *)info { 408 | dispatch_sync(self.danmuQueue, ^{ 409 | __block QHDanmuLabel *danmuLabel = [QHDanmuLabel createWithInfo:info inView:self.danmuView]; 410 | if ([danmuLabel isMoveModeFadeOut]) { 411 | [self p_getFadeBestChannel:danmuLabel completion:^(NSUInteger idx, CGFloat offsetY) { 412 | if (idx != NSNotFound) { 413 | [danmuLabel setDanmuChannel:idx offset:offsetY]; 414 | } 415 | }]; 416 | } 417 | else { 418 | [self p_getRollBestChannel:danmuLabel completion:^(NSUInteger idx, CGFloat offsetX) { 419 | if (idx != NSNotFound) { 420 | [danmuLabel setDanmuChannel:idx offset:offsetX]; 421 | } 422 | }]; 423 | } 424 | }); 425 | } 426 | 427 | - (void)resetDanmuWithFrame:(CGRect)frame data:(NSArray *)infos inView:(UIView *)view durationTime:(NSUInteger)time { 428 | self.frame = frame; 429 | if (infos != nil) 430 | self.infos = [infos mutableCopy]; 431 | self.superView = view; 432 | self.durationTime = time; 433 | 434 | [self p_initInfo]; 435 | } 436 | 437 | - (void)resetDanmuWithFrame:(CGRect)frame { 438 | self.frame = frame; 439 | [self p_initInfo]; 440 | } 441 | 442 | - (void)resetDanmuInfos:(NSArray *)infos { 443 | NSAssert(infos != nil, @"传入的弹幕信息不能为nil"); 444 | self.infos = nil; 445 | self.infos = [infos mutableCopy]; 446 | } 447 | 448 | @end 449 | -------------------------------------------------------------------------------- /QHDanumuDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 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 | 47 | 59 | 71 | 83 | 95 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | -------------------------------------------------------------------------------- /QHDanumuDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 743651191B4E45CA0055E399 /* QHDanmuOperateView.m in Sources */ = {isa = PBXBuildFile; fileRef = 743651161B4E45CA0055E399 /* QHDanmuOperateView.m */; }; 11 | 7436511A1B4E45CA0055E399 /* QHDanmuSendView.m in Sources */ = {isa = PBXBuildFile; fileRef = 743651181B4E45CA0055E399 /* QHDanmuSendView.m */; }; 12 | 7436511C1B4E56220055E399 /* QHDanmuSource.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7436511B1B4E56220055E399 /* QHDanmuSource.plist */; }; 13 | 7436511E1B4E70500055E399 /* README.md in Sources */ = {isa = PBXBuildFile; fileRef = 7436511D1B4E70500055E399 /* README.md */; }; 14 | 744871821B3F7E0700DB22B7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 744871811B3F7E0700DB22B7 /* main.m */; }; 15 | 744871851B3F7E0700DB22B7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 744871841B3F7E0700DB22B7 /* AppDelegate.m */; }; 16 | 744871881B3F7E0700DB22B7 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 744871871B3F7E0700DB22B7 /* ViewController.m */; }; 17 | 7448718B1B3F7E0700DB22B7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 744871891B3F7E0700DB22B7 /* Main.storyboard */; }; 18 | 7448718D1B3F7E0700DB22B7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7448718C1B3F7E0700DB22B7 /* Images.xcassets */; }; 19 | 744871901B3F7E0700DB22B7 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7448718E1B3F7E0700DB22B7 /* LaunchScreen.xib */; }; 20 | 7448719C1B3F7E0700DB22B7 /* QHDanumuDemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7448719B1B3F7E0700DB22B7 /* QHDanumuDemoTests.m */; }; 21 | 744871AC1B3F84C600DB22B7 /* QHDanmuUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 744871AB1B3F84C600DB22B7 /* QHDanmuUtil.m */; }; 22 | 744871AF1B3F896100DB22B7 /* QHDanmuManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 744871AE1B3F896100DB22B7 /* QHDanmuManager.m */; }; 23 | 744871B21B3F898700DB22B7 /* QHDanmuView.m in Sources */ = {isa = PBXBuildFile; fileRef = 744871B11B3F898700DB22B7 /* QHDanmuView.m */; }; 24 | 7463F96A1B45430A000E2150 /* QHDanmuLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 7463F9691B45430A000E2150 /* QHDanmuLabel.m */; }; 25 | 747652651B4F5CA700D8DD9D /* NSTimer+EOCBlocksSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 747652641B4F5CA700D8DD9D /* NSTimer+EOCBlocksSupport.m */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXContainerItemProxy section */ 29 | 744871961B3F7E0700DB22B7 /* PBXContainerItemProxy */ = { 30 | isa = PBXContainerItemProxy; 31 | containerPortal = 744871741B3F7E0700DB22B7 /* Project object */; 32 | proxyType = 1; 33 | remoteGlobalIDString = 7448717B1B3F7E0700DB22B7; 34 | remoteInfo = QHDanumuDemo; 35 | }; 36 | /* End PBXContainerItemProxy section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | 743651151B4E45CA0055E399 /* QHDanmuOperateView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QHDanmuOperateView.h; sourceTree = ""; }; 40 | 743651161B4E45CA0055E399 /* QHDanmuOperateView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QHDanmuOperateView.m; sourceTree = ""; }; 41 | 743651171B4E45CA0055E399 /* QHDanmuSendView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QHDanmuSendView.h; sourceTree = ""; }; 42 | 743651181B4E45CA0055E399 /* QHDanmuSendView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QHDanmuSendView.m; sourceTree = ""; }; 43 | 7436511B1B4E56220055E399 /* QHDanmuSource.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = QHDanmuSource.plist; sourceTree = ""; }; 44 | 7436511D1B4E70500055E399 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; 45 | 7448717C1B3F7E0700DB22B7 /* QHDanumuDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QHDanumuDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | 744871801B3F7E0700DB22B7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 47 | 744871811B3F7E0700DB22B7 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 48 | 744871831B3F7E0700DB22B7 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 49 | 744871841B3F7E0700DB22B7 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 50 | 744871861B3F7E0700DB22B7 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 51 | 744871871B3F7E0700DB22B7 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 52 | 7448718A1B3F7E0700DB22B7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 53 | 7448718C1B3F7E0700DB22B7 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 54 | 7448718F1B3F7E0700DB22B7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 55 | 744871951B3F7E0700DB22B7 /* QHDanumuDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = QHDanumuDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | 7448719A1B3F7E0700DB22B7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | 7448719B1B3F7E0700DB22B7 /* QHDanumuDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QHDanumuDemoTests.m; sourceTree = ""; }; 58 | 744871AA1B3F84C600DB22B7 /* QHDanmuUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QHDanmuUtil.h; sourceTree = ""; }; 59 | 744871AB1B3F84C600DB22B7 /* QHDanmuUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QHDanmuUtil.m; sourceTree = ""; }; 60 | 744871AD1B3F896100DB22B7 /* QHDanmuManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QHDanmuManager.h; sourceTree = ""; }; 61 | 744871AE1B3F896100DB22B7 /* QHDanmuManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QHDanmuManager.m; sourceTree = ""; }; 62 | 744871B01B3F898700DB22B7 /* QHDanmuView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QHDanmuView.h; sourceTree = ""; }; 63 | 744871B11B3F898700DB22B7 /* QHDanmuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QHDanmuView.m; sourceTree = ""; }; 64 | 7463F9681B45430A000E2150 /* QHDanmuLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QHDanmuLabel.h; sourceTree = ""; }; 65 | 7463F9691B45430A000E2150 /* QHDanmuLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QHDanmuLabel.m; sourceTree = ""; }; 66 | 747652631B4F5CA700D8DD9D /* NSTimer+EOCBlocksSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSTimer+EOCBlocksSupport.h"; sourceTree = ""; }; 67 | 747652641B4F5CA700D8DD9D /* NSTimer+EOCBlocksSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSTimer+EOCBlocksSupport.m"; sourceTree = ""; }; 68 | /* End PBXFileReference section */ 69 | 70 | /* Begin PBXFrameworksBuildPhase section */ 71 | 744871791B3F7E0700DB22B7 /* Frameworks */ = { 72 | isa = PBXFrameworksBuildPhase; 73 | buildActionMask = 2147483647; 74 | files = ( 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | 744871921B3F7E0700DB22B7 /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | /* End PBXFrameworksBuildPhase section */ 86 | 87 | /* Begin PBXGroup section */ 88 | 743651141B4E45CA0055E399 /* QHDanmuSend */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 743651171B4E45CA0055E399 /* QHDanmuSendView.h */, 92 | 743651181B4E45CA0055E399 /* QHDanmuSendView.m */, 93 | 743651151B4E45CA0055E399 /* QHDanmuOperateView.h */, 94 | 743651161B4E45CA0055E399 /* QHDanmuOperateView.m */, 95 | ); 96 | path = QHDanmuSend; 97 | sourceTree = ""; 98 | }; 99 | 744871731B3F7E0700DB22B7 = { 100 | isa = PBXGroup; 101 | children = ( 102 | 7448717E1B3F7E0700DB22B7 /* QHDanumuDemo */, 103 | 744871981B3F7E0700DB22B7 /* QHDanumuDemoTests */, 104 | 7448717D1B3F7E0700DB22B7 /* Products */, 105 | ); 106 | sourceTree = ""; 107 | }; 108 | 7448717D1B3F7E0700DB22B7 /* Products */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 7448717C1B3F7E0700DB22B7 /* QHDanumuDemo.app */, 112 | 744871951B3F7E0700DB22B7 /* QHDanumuDemoTests.xctest */, 113 | ); 114 | name = Products; 115 | sourceTree = ""; 116 | }; 117 | 7448717E1B3F7E0700DB22B7 /* QHDanumuDemo */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 7436511D1B4E70500055E399 /* README.md */, 121 | 743651141B4E45CA0055E399 /* QHDanmuSend */, 122 | 744871A51B3F7E2600DB22B7 /* QHDanmu */, 123 | 747652621B4F5C4A00D8DD9D /* Category */, 124 | 744871831B3F7E0700DB22B7 /* AppDelegate.h */, 125 | 744871841B3F7E0700DB22B7 /* AppDelegate.m */, 126 | 744871861B3F7E0700DB22B7 /* ViewController.h */, 127 | 744871871B3F7E0700DB22B7 /* ViewController.m */, 128 | 7436511B1B4E56220055E399 /* QHDanmuSource.plist */, 129 | 744871891B3F7E0700DB22B7 /* Main.storyboard */, 130 | 7448718C1B3F7E0700DB22B7 /* Images.xcassets */, 131 | 7448718E1B3F7E0700DB22B7 /* LaunchScreen.xib */, 132 | 7448717F1B3F7E0700DB22B7 /* Supporting Files */, 133 | ); 134 | path = QHDanumuDemo; 135 | sourceTree = ""; 136 | }; 137 | 7448717F1B3F7E0700DB22B7 /* Supporting Files */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | 744871801B3F7E0700DB22B7 /* Info.plist */, 141 | 744871811B3F7E0700DB22B7 /* main.m */, 142 | ); 143 | name = "Supporting Files"; 144 | sourceTree = ""; 145 | }; 146 | 744871981B3F7E0700DB22B7 /* QHDanumuDemoTests */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | 7448719B1B3F7E0700DB22B7 /* QHDanumuDemoTests.m */, 150 | 744871991B3F7E0700DB22B7 /* Supporting Files */, 151 | ); 152 | path = QHDanumuDemoTests; 153 | sourceTree = ""; 154 | }; 155 | 744871991B3F7E0700DB22B7 /* Supporting Files */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | 7448719A1B3F7E0700DB22B7 /* Info.plist */, 159 | ); 160 | name = "Supporting Files"; 161 | sourceTree = ""; 162 | }; 163 | 744871A51B3F7E2600DB22B7 /* QHDanmu */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | 744871AD1B3F896100DB22B7 /* QHDanmuManager.h */, 167 | 744871AE1B3F896100DB22B7 /* QHDanmuManager.m */, 168 | 7463F9681B45430A000E2150 /* QHDanmuLabel.h */, 169 | 7463F9691B45430A000E2150 /* QHDanmuLabel.m */, 170 | 744871B01B3F898700DB22B7 /* QHDanmuView.h */, 171 | 744871B11B3F898700DB22B7 /* QHDanmuView.m */, 172 | 744871AA1B3F84C600DB22B7 /* QHDanmuUtil.h */, 173 | 744871AB1B3F84C600DB22B7 /* QHDanmuUtil.m */, 174 | ); 175 | path = QHDanmu; 176 | sourceTree = ""; 177 | }; 178 | 747652621B4F5C4A00D8DD9D /* Category */ = { 179 | isa = PBXGroup; 180 | children = ( 181 | 747652631B4F5CA700D8DD9D /* NSTimer+EOCBlocksSupport.h */, 182 | 747652641B4F5CA700D8DD9D /* NSTimer+EOCBlocksSupport.m */, 183 | ); 184 | name = Category; 185 | sourceTree = ""; 186 | }; 187 | /* End PBXGroup section */ 188 | 189 | /* Begin PBXNativeTarget section */ 190 | 7448717B1B3F7E0700DB22B7 /* QHDanumuDemo */ = { 191 | isa = PBXNativeTarget; 192 | buildConfigurationList = 7448719F1B3F7E0700DB22B7 /* Build configuration list for PBXNativeTarget "QHDanumuDemo" */; 193 | buildPhases = ( 194 | 744871781B3F7E0700DB22B7 /* Sources */, 195 | 744871791B3F7E0700DB22B7 /* Frameworks */, 196 | 7448717A1B3F7E0700DB22B7 /* Resources */, 197 | ); 198 | buildRules = ( 199 | ); 200 | dependencies = ( 201 | ); 202 | name = QHDanumuDemo; 203 | productName = QHDanumuDemo; 204 | productReference = 7448717C1B3F7E0700DB22B7 /* QHDanumuDemo.app */; 205 | productType = "com.apple.product-type.application"; 206 | }; 207 | 744871941B3F7E0700DB22B7 /* QHDanumuDemoTests */ = { 208 | isa = PBXNativeTarget; 209 | buildConfigurationList = 744871A21B3F7E0700DB22B7 /* Build configuration list for PBXNativeTarget "QHDanumuDemoTests" */; 210 | buildPhases = ( 211 | 744871911B3F7E0700DB22B7 /* Sources */, 212 | 744871921B3F7E0700DB22B7 /* Frameworks */, 213 | 744871931B3F7E0700DB22B7 /* Resources */, 214 | ); 215 | buildRules = ( 216 | ); 217 | dependencies = ( 218 | 744871971B3F7E0700DB22B7 /* PBXTargetDependency */, 219 | ); 220 | name = QHDanumuDemoTests; 221 | productName = QHDanumuDemoTests; 222 | productReference = 744871951B3F7E0700DB22B7 /* QHDanumuDemoTests.xctest */; 223 | productType = "com.apple.product-type.bundle.unit-test"; 224 | }; 225 | /* End PBXNativeTarget section */ 226 | 227 | /* Begin PBXProject section */ 228 | 744871741B3F7E0700DB22B7 /* Project object */ = { 229 | isa = PBXProject; 230 | attributes = { 231 | LastUpgradeCheck = 1010; 232 | ORGANIZATIONNAME = chen; 233 | TargetAttributes = { 234 | 7448717B1B3F7E0700DB22B7 = { 235 | CreatedOnToolsVersion = 6.1; 236 | DevelopmentTeam = 559F36FNHV; 237 | }; 238 | 744871941B3F7E0700DB22B7 = { 239 | CreatedOnToolsVersion = 6.1; 240 | DevelopmentTeam = 559F36FNHV; 241 | TestTargetID = 7448717B1B3F7E0700DB22B7; 242 | }; 243 | }; 244 | }; 245 | buildConfigurationList = 744871771B3F7E0700DB22B7 /* Build configuration list for PBXProject "QHDanumuDemo" */; 246 | compatibilityVersion = "Xcode 3.2"; 247 | developmentRegion = English; 248 | hasScannedForEncodings = 0; 249 | knownRegions = ( 250 | en, 251 | Base, 252 | ); 253 | mainGroup = 744871731B3F7E0700DB22B7; 254 | productRefGroup = 7448717D1B3F7E0700DB22B7 /* Products */; 255 | projectDirPath = ""; 256 | projectRoot = ""; 257 | targets = ( 258 | 7448717B1B3F7E0700DB22B7 /* QHDanumuDemo */, 259 | 744871941B3F7E0700DB22B7 /* QHDanumuDemoTests */, 260 | ); 261 | }; 262 | /* End PBXProject section */ 263 | 264 | /* Begin PBXResourcesBuildPhase section */ 265 | 7448717A1B3F7E0700DB22B7 /* Resources */ = { 266 | isa = PBXResourcesBuildPhase; 267 | buildActionMask = 2147483647; 268 | files = ( 269 | 7436511C1B4E56220055E399 /* QHDanmuSource.plist in Resources */, 270 | 7448718B1B3F7E0700DB22B7 /* Main.storyboard in Resources */, 271 | 744871901B3F7E0700DB22B7 /* LaunchScreen.xib in Resources */, 272 | 7448718D1B3F7E0700DB22B7 /* Images.xcassets in Resources */, 273 | ); 274 | runOnlyForDeploymentPostprocessing = 0; 275 | }; 276 | 744871931B3F7E0700DB22B7 /* Resources */ = { 277 | isa = PBXResourcesBuildPhase; 278 | buildActionMask = 2147483647; 279 | files = ( 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | }; 283 | /* End PBXResourcesBuildPhase section */ 284 | 285 | /* Begin PBXSourcesBuildPhase section */ 286 | 744871781B3F7E0700DB22B7 /* Sources */ = { 287 | isa = PBXSourcesBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | 744871AF1B3F896100DB22B7 /* QHDanmuManager.m in Sources */, 291 | 744871881B3F7E0700DB22B7 /* ViewController.m in Sources */, 292 | 744871851B3F7E0700DB22B7 /* AppDelegate.m in Sources */, 293 | 7436511A1B4E45CA0055E399 /* QHDanmuSendView.m in Sources */, 294 | 744871821B3F7E0700DB22B7 /* main.m in Sources */, 295 | 7463F96A1B45430A000E2150 /* QHDanmuLabel.m in Sources */, 296 | 747652651B4F5CA700D8DD9D /* NSTimer+EOCBlocksSupport.m in Sources */, 297 | 744871B21B3F898700DB22B7 /* QHDanmuView.m in Sources */, 298 | 743651191B4E45CA0055E399 /* QHDanmuOperateView.m in Sources */, 299 | 7436511E1B4E70500055E399 /* README.md in Sources */, 300 | 744871AC1B3F84C600DB22B7 /* QHDanmuUtil.m in Sources */, 301 | ); 302 | runOnlyForDeploymentPostprocessing = 0; 303 | }; 304 | 744871911B3F7E0700DB22B7 /* Sources */ = { 305 | isa = PBXSourcesBuildPhase; 306 | buildActionMask = 2147483647; 307 | files = ( 308 | 7448719C1B3F7E0700DB22B7 /* QHDanumuDemoTests.m in Sources */, 309 | ); 310 | runOnlyForDeploymentPostprocessing = 0; 311 | }; 312 | /* End PBXSourcesBuildPhase section */ 313 | 314 | /* Begin PBXTargetDependency section */ 315 | 744871971B3F7E0700DB22B7 /* PBXTargetDependency */ = { 316 | isa = PBXTargetDependency; 317 | target = 7448717B1B3F7E0700DB22B7 /* QHDanumuDemo */; 318 | targetProxy = 744871961B3F7E0700DB22B7 /* PBXContainerItemProxy */; 319 | }; 320 | /* End PBXTargetDependency section */ 321 | 322 | /* Begin PBXVariantGroup section */ 323 | 744871891B3F7E0700DB22B7 /* Main.storyboard */ = { 324 | isa = PBXVariantGroup; 325 | children = ( 326 | 7448718A1B3F7E0700DB22B7 /* Base */, 327 | ); 328 | name = Main.storyboard; 329 | sourceTree = ""; 330 | }; 331 | 7448718E1B3F7E0700DB22B7 /* LaunchScreen.xib */ = { 332 | isa = PBXVariantGroup; 333 | children = ( 334 | 7448718F1B3F7E0700DB22B7 /* Base */, 335 | ); 336 | name = LaunchScreen.xib; 337 | sourceTree = ""; 338 | }; 339 | /* End PBXVariantGroup section */ 340 | 341 | /* Begin XCBuildConfiguration section */ 342 | 7448719D1B3F7E0700DB22B7 /* Debug */ = { 343 | isa = XCBuildConfiguration; 344 | buildSettings = { 345 | ALWAYS_SEARCH_USER_PATHS = NO; 346 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 347 | CLANG_CXX_LIBRARY = "libc++"; 348 | CLANG_ENABLE_MODULES = YES; 349 | CLANG_ENABLE_OBJC_ARC = YES; 350 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 351 | CLANG_WARN_BOOL_CONVERSION = YES; 352 | CLANG_WARN_COMMA = YES; 353 | CLANG_WARN_CONSTANT_CONVERSION = YES; 354 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 355 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 356 | CLANG_WARN_EMPTY_BODY = YES; 357 | CLANG_WARN_ENUM_CONVERSION = YES; 358 | CLANG_WARN_INFINITE_RECURSION = YES; 359 | CLANG_WARN_INT_CONVERSION = YES; 360 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 361 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 362 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 363 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 364 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 365 | CLANG_WARN_STRICT_PROTOTYPES = YES; 366 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 367 | CLANG_WARN_UNREACHABLE_CODE = YES; 368 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 369 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 370 | COPY_PHASE_STRIP = NO; 371 | ENABLE_STRICT_OBJC_MSGSEND = YES; 372 | ENABLE_TESTABILITY = YES; 373 | GCC_C_LANGUAGE_STANDARD = gnu99; 374 | GCC_DYNAMIC_NO_PIC = NO; 375 | GCC_NO_COMMON_BLOCKS = YES; 376 | GCC_OPTIMIZATION_LEVEL = 0; 377 | GCC_PREPROCESSOR_DEFINITIONS = ( 378 | "DEBUG=1", 379 | "$(inherited)", 380 | ); 381 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 382 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 383 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 384 | GCC_WARN_UNDECLARED_SELECTOR = YES; 385 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 386 | GCC_WARN_UNUSED_FUNCTION = YES; 387 | GCC_WARN_UNUSED_VARIABLE = YES; 388 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 389 | MTL_ENABLE_DEBUG_INFO = YES; 390 | ONLY_ACTIVE_ARCH = YES; 391 | SDKROOT = iphoneos; 392 | }; 393 | name = Debug; 394 | }; 395 | 7448719E1B3F7E0700DB22B7 /* Release */ = { 396 | isa = XCBuildConfiguration; 397 | buildSettings = { 398 | ALWAYS_SEARCH_USER_PATHS = NO; 399 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 400 | CLANG_CXX_LIBRARY = "libc++"; 401 | CLANG_ENABLE_MODULES = YES; 402 | CLANG_ENABLE_OBJC_ARC = YES; 403 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 404 | CLANG_WARN_BOOL_CONVERSION = YES; 405 | CLANG_WARN_COMMA = YES; 406 | CLANG_WARN_CONSTANT_CONVERSION = YES; 407 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 408 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 409 | CLANG_WARN_EMPTY_BODY = YES; 410 | CLANG_WARN_ENUM_CONVERSION = YES; 411 | CLANG_WARN_INFINITE_RECURSION = YES; 412 | CLANG_WARN_INT_CONVERSION = YES; 413 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 414 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 415 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 416 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 417 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 418 | CLANG_WARN_STRICT_PROTOTYPES = YES; 419 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 420 | CLANG_WARN_UNREACHABLE_CODE = YES; 421 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 422 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 423 | COPY_PHASE_STRIP = YES; 424 | ENABLE_NS_ASSERTIONS = NO; 425 | ENABLE_STRICT_OBJC_MSGSEND = YES; 426 | GCC_C_LANGUAGE_STANDARD = gnu99; 427 | GCC_NO_COMMON_BLOCKS = YES; 428 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 429 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 430 | GCC_WARN_UNDECLARED_SELECTOR = YES; 431 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 432 | GCC_WARN_UNUSED_FUNCTION = YES; 433 | GCC_WARN_UNUSED_VARIABLE = YES; 434 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 435 | MTL_ENABLE_DEBUG_INFO = NO; 436 | SDKROOT = iphoneos; 437 | VALIDATE_PRODUCT = YES; 438 | }; 439 | name = Release; 440 | }; 441 | 744871A01B3F7E0700DB22B7 /* Debug */ = { 442 | isa = XCBuildConfiguration; 443 | buildSettings = { 444 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 445 | DEVELOPMENT_TEAM = 559F36FNHV; 446 | INFOPLIST_FILE = QHDanumuDemo/Info.plist; 447 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 448 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 449 | PRODUCT_BUNDLE_IDENTIFIER = "com.chen.$(PRODUCT_NAME:rfc1034identifier)"; 450 | PRODUCT_NAME = "$(TARGET_NAME)"; 451 | }; 452 | name = Debug; 453 | }; 454 | 744871A11B3F7E0700DB22B7 /* Release */ = { 455 | isa = XCBuildConfiguration; 456 | buildSettings = { 457 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 458 | DEVELOPMENT_TEAM = 559F36FNHV; 459 | INFOPLIST_FILE = QHDanumuDemo/Info.plist; 460 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 461 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 462 | PRODUCT_BUNDLE_IDENTIFIER = "com.chen.$(PRODUCT_NAME:rfc1034identifier)"; 463 | PRODUCT_NAME = "$(TARGET_NAME)"; 464 | }; 465 | name = Release; 466 | }; 467 | 744871A31B3F7E0700DB22B7 /* Debug */ = { 468 | isa = XCBuildConfiguration; 469 | buildSettings = { 470 | BUNDLE_LOADER = "$(TEST_HOST)"; 471 | DEVELOPMENT_TEAM = 559F36FNHV; 472 | FRAMEWORK_SEARCH_PATHS = ( 473 | "$(SDKROOT)/Developer/Library/Frameworks", 474 | "$(inherited)", 475 | ); 476 | GCC_PREPROCESSOR_DEFINITIONS = ( 477 | "DEBUG=1", 478 | "$(inherited)", 479 | ); 480 | INFOPLIST_FILE = QHDanumuDemoTests/Info.plist; 481 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 482 | PRODUCT_BUNDLE_IDENTIFIER = "com.chen.$(PRODUCT_NAME:rfc1034identifier)"; 483 | PRODUCT_NAME = "$(TARGET_NAME)"; 484 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/QHDanumuDemo.app/QHDanumuDemo"; 485 | }; 486 | name = Debug; 487 | }; 488 | 744871A41B3F7E0700DB22B7 /* Release */ = { 489 | isa = XCBuildConfiguration; 490 | buildSettings = { 491 | BUNDLE_LOADER = "$(TEST_HOST)"; 492 | DEVELOPMENT_TEAM = 559F36FNHV; 493 | FRAMEWORK_SEARCH_PATHS = ( 494 | "$(SDKROOT)/Developer/Library/Frameworks", 495 | "$(inherited)", 496 | ); 497 | INFOPLIST_FILE = QHDanumuDemoTests/Info.plist; 498 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 499 | PRODUCT_BUNDLE_IDENTIFIER = "com.chen.$(PRODUCT_NAME:rfc1034identifier)"; 500 | PRODUCT_NAME = "$(TARGET_NAME)"; 501 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/QHDanumuDemo.app/QHDanumuDemo"; 502 | }; 503 | name = Release; 504 | }; 505 | /* End XCBuildConfiguration section */ 506 | 507 | /* Begin XCConfigurationList section */ 508 | 744871771B3F7E0700DB22B7 /* Build configuration list for PBXProject "QHDanumuDemo" */ = { 509 | isa = XCConfigurationList; 510 | buildConfigurations = ( 511 | 7448719D1B3F7E0700DB22B7 /* Debug */, 512 | 7448719E1B3F7E0700DB22B7 /* Release */, 513 | ); 514 | defaultConfigurationIsVisible = 0; 515 | defaultConfigurationName = Release; 516 | }; 517 | 7448719F1B3F7E0700DB22B7 /* Build configuration list for PBXNativeTarget "QHDanumuDemo" */ = { 518 | isa = XCConfigurationList; 519 | buildConfigurations = ( 520 | 744871A01B3F7E0700DB22B7 /* Debug */, 521 | 744871A11B3F7E0700DB22B7 /* Release */, 522 | ); 523 | defaultConfigurationIsVisible = 0; 524 | defaultConfigurationName = Release; 525 | }; 526 | 744871A21B3F7E0700DB22B7 /* Build configuration list for PBXNativeTarget "QHDanumuDemoTests" */ = { 527 | isa = XCConfigurationList; 528 | buildConfigurations = ( 529 | 744871A31B3F7E0700DB22B7 /* Debug */, 530 | 744871A41B3F7E0700DB22B7 /* Release */, 531 | ); 532 | defaultConfigurationIsVisible = 0; 533 | defaultConfigurationName = Release; 534 | }; 535 | /* End XCConfigurationList section */ 536 | }; 537 | rootObject = 744871741B3F7E0700DB22B7 /* Project object */; 538 | } 539 | --------------------------------------------------------------------------------