├── 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 | 
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 |
--------------------------------------------------------------------------------