Showing preview only (1,310K chars total). Download the full file or copy to clipboard to get everything.
Repository: ibireme/YYText
Branch: master
Commit: 7bd2aa414147
Files: 129
Total size: 1.2 MB
Directory structure:
gitextract_s7a5jp6t/
├── .gitignore
├── .travis.yml
├── Demo/
│ ├── YYTextDemo/
│ │ ├── AppDelegate.h
│ │ ├── AppDelegate.m
│ │ ├── Assets.xcassets/
│ │ │ └── AppIcon.appiconset/
│ │ │ └── Contents.json
│ │ ├── Base.lproj/
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ │ ├── CALayer+YYAdd.h
│ │ ├── CALayer+YYAdd.m
│ │ ├── EmoticonQQ.bundle/
│ │ │ └── info.plist
│ │ ├── Info.plist
│ │ ├── NSBundle+YYAdd.h
│ │ ├── NSBundle+YYAdd.m
│ │ ├── NSData+YYAdd.h
│ │ ├── NSData+YYAdd.m
│ │ ├── NSString+YYAdd.h
│ │ ├── NSString+YYAdd.m
│ │ ├── UIControl+YYAdd.h
│ │ ├── UIControl+YYAdd.m
│ │ ├── UIGestureRecognizer+YYAdd.h
│ │ ├── UIGestureRecognizer+YYAdd.m
│ │ ├── UIImage+YYWebImage.h
│ │ ├── UIImage+YYWebImage.m
│ │ ├── UIView+YYAdd.h
│ │ ├── UIView+YYAdd.m
│ │ ├── ViewController.h
│ │ ├── ViewController.m
│ │ ├── YYFPSLabel.h
│ │ ├── YYFPSLabel.m
│ │ ├── YYGestureRecognizer.h
│ │ ├── YYGestureRecognizer.m
│ │ ├── YYImage/
│ │ │ ├── Animated image support.txt
│ │ │ ├── YYAnimatedImageView.h
│ │ │ ├── YYAnimatedImageView.m
│ │ │ ├── YYFrameImage.h
│ │ │ ├── YYFrameImage.m
│ │ │ ├── YYImage.h
│ │ │ ├── YYImage.m
│ │ │ ├── YYImageCoder.h
│ │ │ ├── YYImageCoder.m
│ │ │ ├── YYSpriteSheetImage.h
│ │ │ └── YYSpriteSheetImage.m
│ │ ├── YYTextAsyncExample.h
│ │ ├── YYTextAsyncExample.m
│ │ ├── YYTextAttachmentExample.h
│ │ ├── YYTextAttachmentExample.m
│ │ ├── YYTextAttributeExample.h
│ │ ├── YYTextAttributeExample.m
│ │ ├── YYTextBindingExample.h
│ │ ├── YYTextBindingExample.m
│ │ ├── YYTextCopyPasteExample.h
│ │ ├── YYTextCopyPasteExample.m
│ │ ├── YYTextEditExample.h
│ │ ├── YYTextEditExample.m
│ │ ├── YYTextEmoticonExample.h
│ │ ├── YYTextEmoticonExample.m
│ │ ├── YYTextExample.h
│ │ ├── YYTextExample.m
│ │ ├── YYTextExampleHelper.h
│ │ ├── YYTextExampleHelper.m
│ │ ├── YYTextMarkdownExample.h
│ │ ├── YYTextMarkdownExample.m
│ │ ├── YYTextRubyExample.h
│ │ ├── YYTextRubyExample.m
│ │ ├── YYTextTagExample.h
│ │ ├── YYTextTagExample.m
│ │ ├── YYTextUndoRedoExample.h
│ │ ├── YYTextUndoRedoExample.m
│ │ ├── YYWeakProxy.h
│ │ ├── YYWeakProxy.m
│ │ └── main.m
│ └── YYTextDemo.xcodeproj/
│ ├── project.pbxproj
│ └── project.xcworkspace/
│ └── contents.xcworkspacedata
├── Framework/
│ ├── Info.plist
│ └── YYText.xcodeproj/
│ ├── project.pbxproj
│ ├── project.xcworkspace/
│ │ └── contents.xcworkspacedata
│ └── xcshareddata/
│ └── xcschemes/
│ └── YYText.xcscheme
├── LICENSE
├── README.md
├── YYText/
│ ├── Component/
│ │ ├── YYTextContainerView.h
│ │ ├── YYTextContainerView.m
│ │ ├── YYTextDebugOption.h
│ │ ├── YYTextDebugOption.m
│ │ ├── YYTextEffectWindow.h
│ │ ├── YYTextEffectWindow.m
│ │ ├── YYTextInput.h
│ │ ├── YYTextInput.m
│ │ ├── YYTextKeyboardManager.h
│ │ ├── YYTextKeyboardManager.m
│ │ ├── YYTextLayout.h
│ │ ├── YYTextLayout.m
│ │ ├── YYTextLine.h
│ │ ├── YYTextLine.m
│ │ ├── YYTextMagnifier.h
│ │ ├── YYTextMagnifier.m
│ │ ├── YYTextSelectionView.h
│ │ └── YYTextSelectionView.m
│ ├── String/
│ │ ├── YYTextArchiver.h
│ │ ├── YYTextArchiver.m
│ │ ├── YYTextAttribute.h
│ │ ├── YYTextAttribute.m
│ │ ├── YYTextParser.h
│ │ ├── YYTextParser.m
│ │ ├── YYTextRubyAnnotation.h
│ │ ├── YYTextRubyAnnotation.m
│ │ ├── YYTextRunDelegate.h
│ │ └── YYTextRunDelegate.m
│ ├── Utility/
│ │ ├── NSAttributedString+YYText.h
│ │ ├── NSAttributedString+YYText.m
│ │ ├── NSParagraphStyle+YYText.h
│ │ ├── NSParagraphStyle+YYText.m
│ │ ├── UIPasteboard+YYText.h
│ │ ├── UIPasteboard+YYText.m
│ │ ├── UIView+YYText.h
│ │ ├── UIView+YYText.m
│ │ ├── YYTextAsyncLayer.h
│ │ ├── YYTextAsyncLayer.m
│ │ ├── YYTextTransaction.h
│ │ ├── YYTextTransaction.m
│ │ ├── YYTextUtilities.h
│ │ ├── YYTextUtilities.m
│ │ ├── YYTextWeakProxy.h
│ │ └── YYTextWeakProxy.m
│ ├── YYLabel.h
│ ├── YYLabel.m
│ ├── YYText.h
│ ├── YYTextView.h
│ └── YYTextView.m
└── YYText.podspec
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# OS X
.DS_Store
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
build/
DerivedData/
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
## Other
*.moved-aside
*.xccheckout
*.xcuserstate
*.xcscmblueprint
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/
================================================
FILE: .travis.yml
================================================
language: objective-c
osx_image: xcode8
xcode_project: Framework/YYText.xcodeproj
xcode_scheme: YYText
before_install:
- env
- xcodebuild -version
- xcodebuild -showsdks
- xcpretty --version
script:
- set -o pipefail
- xcodebuild clean build -project "$TRAVIS_XCODE_PROJECT" -scheme "$TRAVIS_XCODE_SCHEME" | xcpretty
================================================
FILE: Demo/YYTextDemo/AppDelegate.h
================================================
//
// AppDelegate.h
// YYTextDemo
//
// Created by ibireme on 15/10/17.
// Copyright © 2015年 ibireme. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
================================================
FILE: Demo/YYTextDemo/AppDelegate.m
================================================
//
// AppDelegate.m
// YYTextDemo
//
// Created by ibireme on 15/10/17.
// Copyright © 2015年 ibireme. All rights reserved.
//
#import "AppDelegate.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application {
// 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.
// 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.
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// 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.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// 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.
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// 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.
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
@end
================================================
FILE: Demo/YYTextDemo/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demo/YYTextDemo/Base.lproj/LaunchScreen.storyboard
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11134" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11106"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
================================================
FILE: Demo/YYTextDemo/Base.lproj/Main.storyboard
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11134" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11106"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
================================================
FILE: Demo/YYTextDemo/CALayer+YYAdd.h
================================================
//
// CALayer+YYAdd.h
// YYKit <https://github.com/ibireme/YYKit>
//
// Created by ibireme on 14/5/10.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
/**
Provides extensions for `CALayer`.
*/
@interface CALayer (YYAdd)
/**
Take snapshot without transform, image's size equals to bounds.
*/
- (UIImage *)snapshotImage;
/**
Take snapshot without transform, PDF's page size equals to bounds.
*/
- (NSData *)snapshotPDF;
/**
Shortcut to set the layer's shadow
@param color Shadow Color
@param offset Shadow offset
@param radius Shadow radius
*/
- (void)setLayerShadow:(UIColor*)color offset:(CGSize)offset radius:(CGFloat)radius;
/**
Remove all sublayers.
*/
- (void)removeAllSublayers;
@property (nonatomic) CGFloat left; ///< Shortcut for frame.origin.x.
@property (nonatomic) CGFloat top; ///< Shortcut for frame.origin.y
@property (nonatomic) CGFloat right; ///< Shortcut for frame.origin.x + frame.size.width
@property (nonatomic) CGFloat bottom; ///< Shortcut for frame.origin.y + frame.size.height
@property (nonatomic) CGFloat width; ///< Shortcut for frame.size.width.
@property (nonatomic) CGFloat height; ///< Shortcut for frame.size.height.
@property (nonatomic) CGPoint center; ///< Shortcut for center.
@property (nonatomic) CGFloat centerX; ///< Shortcut for center.x
@property (nonatomic) CGFloat centerY; ///< Shortcut for center.y
@property (nonatomic) CGPoint origin; ///< Shortcut for frame.origin.
@property (nonatomic, getter=frameSize, setter=setFrameSize:) CGSize size; ///< Shortcut for frame.size.
@property (nonatomic) CGFloat transformRotation; ///< key path "tranform.rotation"
@property (nonatomic) CGFloat transformRotationX; ///< key path "tranform.rotation.x"
@property (nonatomic) CGFloat transformRotationY; ///< key path "tranform.rotation.y"
@property (nonatomic) CGFloat transformRotationZ; ///< key path "tranform.rotation.z"
@property (nonatomic) CGFloat transformScale; ///< key path "tranform.scale"
@property (nonatomic) CGFloat transformScaleX; ///< key path "tranform.scale.x"
@property (nonatomic) CGFloat transformScaleY; ///< key path "tranform.scale.y"
@property (nonatomic) CGFloat transformScaleZ; ///< key path "tranform.scale.z"
@property (nonatomic) CGFloat transformTranslationX; ///< key path "tranform.translation.x"
@property (nonatomic) CGFloat transformTranslationY; ///< key path "tranform.translation.y"
@property (nonatomic) CGFloat transformTranslationZ; ///< key path "tranform.translation.z"
/**
Shortcut for transform.m34, -1/1000 is a good value.
It should be set before other transform shortcut.
*/
@property (nonatomic, assign) CGFloat transformDepth;
/**
Add a fade animation to layer's contents when the contents is changed.
@param duration Animation duration
@param curve Animation curve.
*/
- (void)addFadeAnimationWithDuration:(NSTimeInterval)duration curve:(UIViewAnimationCurve)curve;
/**
Cancel fade animation which is added with "-addFadeAnimationWithDuration:curve:".
*/
- (void)removePreviousFadeAnimation;
@end
================================================
FILE: Demo/YYTextDemo/CALayer+YYAdd.m
================================================
//
// CALayer+YYAdd.m
// YYKit <https://github.com/ibireme/YYKit>
//
// Created by ibireme on 14/5/10.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "CALayer+YYAdd.h"
@implementation CALayer (YYAdd)
- (UIImage *)snapshotImage {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
[self renderInContext:context];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (NSData *)snapshotPDF {
CGRect bounds = self.bounds;
NSMutableData* data = [NSMutableData data];
CGDataConsumerRef consumer = CGDataConsumerCreateWithCFData((__bridge CFMutableDataRef)data);
CGContextRef context = CGPDFContextCreate(consumer, &bounds, NULL);
CGDataConsumerRelease(consumer);
if (!context) return nil;
CGPDFContextBeginPage(context, NULL);
CGContextTranslateCTM(context, 0, bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
[self renderInContext:context];
CGPDFContextEndPage(context);
CGPDFContextClose(context);
CGContextRelease(context);
return data;
}
- (void)setLayerShadow:(UIColor*)color offset:(CGSize)offset radius:(CGFloat)radius {
self.shadowColor = color.CGColor;
self.shadowOffset = offset;
self.shadowRadius = radius;
self.shadowOpacity = 1;
self.shouldRasterize = YES;
self.rasterizationScale = [UIScreen mainScreen].scale;
}
- (void)removeAllSublayers {
while (self.sublayers.count) {
[self.sublayers.lastObject removeFromSuperlayer];
}
}
- (CGFloat)left {
return self.frame.origin.x;
}
- (void)setLeft:(CGFloat)x {
CGRect frame = self.frame;
frame.origin.x = x;
self.frame = frame;
}
- (CGFloat)top {
return self.frame.origin.y;
}
- (void)setTop:(CGFloat)y {
CGRect frame = self.frame;
frame.origin.y = y;
self.frame = frame;
}
- (CGFloat)right {
return self.frame.origin.x + self.frame.size.width;
}
- (void)setRight:(CGFloat)right {
CGRect frame = self.frame;
frame.origin.x = right - frame.size.width;
self.frame = frame;
}
- (CGFloat)bottom {
return self.frame.origin.y + self.frame.size.height;
}
- (void)setBottom:(CGFloat)bottom {
CGRect frame = self.frame;
frame.origin.y = bottom - frame.size.height;
self.frame = frame;
}
- (CGFloat)width {
return self.frame.size.width;
}
- (void)setWidth:(CGFloat)width {
CGRect frame = self.frame;
frame.size.width = width;
self.frame = frame;
}
- (CGFloat)height {
return self.frame.size.height;
}
- (void)setHeight:(CGFloat)height {
CGRect frame = self.frame;
frame.size.height = height;
self.frame = frame;
}
- (CGPoint)center {
return CGPointMake(self.frame.origin.x + self.frame.size.width * 0.5,
self.frame.origin.y + self.frame.size.height * 0.5);
}
- (void)setCenter:(CGPoint)center {
CGRect frame = self.frame;
frame.origin.x = center.x - frame.size.width * 0.5;
frame.origin.y = center.y - frame.size.height * 0.5;
self.frame = frame;
}
- (CGFloat)centerX {
return self.frame.origin.x + self.frame.size.width * 0.5;
}
- (void)setCenterX:(CGFloat)centerX {
CGRect frame = self.frame;
frame.origin.x = centerX - frame.size.width * 0.5;
self.frame = frame;
}
- (CGFloat)centerY {
return self.frame.origin.y + self.frame.size.height * 0.5;
}
- (void)setCenterY:(CGFloat)centerY {
CGRect frame = self.frame;
frame.origin.y = centerY - frame.size.height * 0.5;
self.frame = frame;
}
- (CGPoint)origin {
return self.frame.origin;
}
- (void)setOrigin:(CGPoint)origin {
CGRect frame = self.frame;
frame.origin = origin;
self.frame = frame;
}
- (CGSize)frameSize {
return self.frame.size;
}
- (void)setFrameSize:(CGSize)size {
CGRect frame = self.frame;
frame.size = size;
self.frame = frame;
}
- (CGFloat)transformRotation {
NSNumber *v = [self valueForKeyPath:@"transform.rotation"];
return v.doubleValue;
}
- (void)setTransformRotation:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.rotation"];
}
- (CGFloat)transformRotationX {
NSNumber *v = [self valueForKeyPath:@"transform.rotation.x"];
return v.doubleValue;
}
- (void)setTransformRotationX:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.rotation.x"];
}
- (CGFloat)transformRotationY {
NSNumber *v = [self valueForKeyPath:@"transform.rotation.y"];
return v.doubleValue;
}
- (void)setTransformRotationY:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.rotation.y"];
}
- (CGFloat)transformRotationZ {
NSNumber *v = [self valueForKeyPath:@"transform.rotation.z"];
return v.doubleValue;
}
- (void)setTransformRotationZ:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.rotation.z"];
}
- (CGFloat)transformScaleX {
NSNumber *v = [self valueForKeyPath:@"transform.scale.x"];
return v.doubleValue;
}
- (void)setTransformScaleX:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.scale.x"];
}
- (CGFloat)transformScaleY {
NSNumber *v = [self valueForKeyPath:@"transform.scale.y"];
return v.doubleValue;
}
- (void)setTransformScaleY:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.scale.y"];
}
- (CGFloat)transformScaleZ {
NSNumber *v = [self valueForKeyPath:@"transform.scale.z"];
return v.doubleValue;
}
- (void)setTransformScaleZ:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.scale.z"];
}
- (CGFloat)transformScale {
NSNumber *v = [self valueForKeyPath:@"transform.scale"];
return v.doubleValue;
}
- (void)setTransformScale:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.scale"];
}
- (CGFloat)transformTranslationX {
NSNumber *v = [self valueForKeyPath:@"transform.translation.x"];
return v.doubleValue;
}
- (void)setTransformTranslationX:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.translation.x"];
}
- (CGFloat)transformTranslationY {
NSNumber *v = [self valueForKeyPath:@"transform.translation.y"];
return v.doubleValue;
}
- (void)setTransformTranslationY:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.translation.y"];
}
- (CGFloat)transformTranslationZ {
NSNumber *v = [self valueForKeyPath:@"transform.translation.z"];
return v.doubleValue;
}
- (void)setTransformTranslationZ:(CGFloat)v {
[self setValue:@(v) forKeyPath:@"transform.translation.z"];
}
- (CGFloat)transformDepth {
return self.transform.m34;
}
- (void)setTransformDepth:(CGFloat)v {
CATransform3D d = self.transform;
d.m34 = v;
self.transform = d;
}
- (void)addFadeAnimationWithDuration:(NSTimeInterval)duration curve:(UIViewAnimationCurve)curve {
if (duration <= 0) return;
NSString *mediaFunction;
switch (curve) {
case UIViewAnimationCurveEaseInOut: {
mediaFunction = kCAMediaTimingFunctionEaseOut;
} break;
case UIViewAnimationCurveEaseIn: {
mediaFunction = kCAMediaTimingFunctionEaseIn;
} break;
case UIViewAnimationCurveEaseOut: {
mediaFunction = kCAMediaTimingFunctionEaseInEaseOut;
} break;
case UIViewAnimationCurveLinear: {
mediaFunction = kCAMediaTimingFunctionLinear;
} break;
default: {
mediaFunction = kCAMediaTimingFunctionLinear;
} break;
}
CATransition *transition = [CATransition animation];
transition.duration = duration;
transition.timingFunction = [CAMediaTimingFunction functionWithName:mediaFunction];
transition.type = kCATransitionFade;
[self addAnimation:transition forKey:@"yykit.fade"];
}
- (void)removePreviousFadeAnimation {
[self removeAnimationForKey:@"yykit.fade"];
}
@end
================================================
FILE: Demo/YYTextDemo/EmoticonQQ.bundle/info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<dict>
<key>/微笑</key>
<string>024</string>
</dict>
<dict>
<key>/撇嘴</key>
<string>041</string>
</dict>
<dict>
<key>/色</key>
<string>020</string>
</dict>
<dict>
<key>/发呆</key>
<string>044</string>
</dict>
<dict>
<key>/得意</key>
<string>022</string>
</dict>
<dict>
<key>/流泪</key>
<string>010</string>
</dict>
<dict>
<key>/害羞</key>
<string>021</string>
</dict>
<dict>
<key>/闭嘴</key>
<string>105</string>
</dict>
<dict>
<key>/睡</key>
<string>036</string>
</dict>
<dict>
<key>/大哭</key>
<string>011</string>
</dict>
<dict>
<key>/尴尬</key>
<string>026</string>
</dict>
<dict>
<key>/发怒</key>
<string>025</string>
</dict>
<dict>
<key>/调皮</key>
<string>002</string>
</dict>
<dict>
<key>/呲牙</key>
<string>001</string>
</dict>
<dict>
<key>/惊讶</key>
<string>034</string>
</dict>
<dict>
<key>/难过</key>
<string>033</string>
</dict>
<dict>
<key>/酷</key>
<string>013</string>
</dict>
<dict>
<key>/冷汗</key>
<string>028</string>
</dict>
<dict>
<key>/抓狂</key>
<string>014</string>
</dict>
<dict>
<key>/吐</key>
<string>023</string>
</dict>
<dict>
<key>/偷笑</key>
<string>004</string>
</dict>
<dict>
<key>/可爱</key>
<string>019</string>
</dict>
<dict>
<key>/白眼</key>
<string>031</string>
</dict>
<dict>
<key>/傲慢</key>
<string>032</string>
</dict>
<dict>
<key>/饥饿</key>
<string>080</string>
</dict>
<dict>
<key>/困</key>
<string>081</string>
</dict>
<dict>
<key>/惊恐</key>
<string>027</string>
</dict>
<dict>
<key>/流汗</key>
<string>003</string>
</dict>
<dict>
<key>/憨笑</key>
<string>038</string>
</dict>
<dict>
<key>/大兵</key>
<string>051</string>
</dict>
<dict>
<key>/奋斗</key>
<string>043</string>
</dict>
<dict>
<key>/咒骂</key>
<string>082</string>
</dict>
<dict>
<key>/疑问</key>
<string>035</string>
</dict>
<dict>
<key>/嘘</key>
<string>012</string>
</dict>
<dict>
<key>/晕</key>
<string>050</string>
</dict>
<dict>
<key>/折磨</key>
<string>014</string>
</dict>
<dict>
<key>/衰</key>
<string>040</string>
</dict>
<dict>
<key>/骷髅</key>
<string>077</string>
</dict>
<dict>
<key>/敲打</key>
<string>006</string>
</dict>
<dict>
<key>/再见</key>
<string>005</string>
</dict>
<dict>
<key>/擦汗</key>
<string>007</string>
</dict>
<dict>
<key>/抠鼻</key>
<string>084</string>
</dict>
<dict>
<key>/鼓掌</key>
<string>085</string>
</dict>
<dict>
<key>/糗大了</key>
<string>086</string>
</dict>
<dict>
<key>/坏笑</key>
<string>047</string>
</dict>
<dict>
<key>/左哼哼</key>
<string>087</string>
</dict>
<dict>
<key>/右哼哼</key>
<string>045</string>
</dict>
<dict>
<key>/哈欠</key>
<string>088</string>
</dict>
<dict>
<key>/鄙视</key>
<string>049</string>
</dict>
<dict>
<key>/委屈</key>
<string>015</string>
</dict>
<dict>
<key>/快哭了</key>
<string>089</string>
</dict>
<dict>
<key>/阴险</key>
<string>042</string>
</dict>
<dict>
<key>/亲亲</key>
<string>037</string>
</dict>
<dict>
<key>/吓</key>
<string>090</string>
</dict>
<dict>
<key>/可怜</key>
<string>052</string>
</dict>
<dict>
<key>/菜刀</key>
<string>018</string>
</dict>
<dict>
<key>/西瓜</key>
<string>061</string>
</dict>
<dict>
<key>/啤酒</key>
<string>062</string>
</dict>
<dict>
<key>/篮球</key>
<string>091</string>
</dict>
<dict>
<key>/乒乓</key>
<string>092</string>
</dict>
<dict>
<key>/咖啡</key>
<string>067</string>
</dict>
<dict>
<key>/饭</key>
<string>059</string>
</dict>
<dict>
<key>/猪头</key>
<string>008</string>
</dict>
<dict>
<key>/玫瑰</key>
<string>009</string>
</dict>
<dict>
<key>/凋谢</key>
<string>061</string>
</dict>
<dict>
<key>/示爱</key>
<string>030</string>
</dict>
<dict>
<key>/爱心</key>
<string>029</string>
</dict>
<dict>
<key>/心碎</key>
<string>073</string>
</dict>
<dict>
<key>/蛋糕</key>
<string>060</string>
</dict>
<dict>
<key>/闪电</key>
<string>079</string>
</dict>
<dict>
<key>/炸弹</key>
<string>017</string>
</dict>
<dict>
<key>/刀</key>
<string>069</string>
</dict>
<dict>
<key>/足球</key>
<string>076</string>
</dict>
<dict>
<key>/瓢虫</key>
<string>063</string>
</dict>
<dict>
<key>/便便</key>
<string>016</string>
</dict>
<dict>
<key>/月亮</key>
<string>068</string>
</dict>
<dict>
<key>/太阳</key>
<string>074</string>
</dict>
<dict>
<key>/礼物</key>
<string>075</string>
</dict>
<dict>
<key>/拥抱</key>
<string>046</string>
</dict>
<dict>
<key>/强</key>
<string>053</string>
</dict>
<dict>
<key>/弱</key>
<string>054</string>
</dict>
<dict>
<key>/握手</key>
<string>055</string>
</dict>
<dict>
<key>/胜利</key>
<string>056</string>
</dict>
<dict>
<key>/抱拳</key>
<string>057</string>
</dict>
<dict>
<key>/勾引</key>
<string>064</string>
</dict>
<dict>
<key>/拳头</key>
<string>072</string>
</dict>
<dict>
<key>/差劲</key>
<string>071</string>
</dict>
<dict>
<key>/爱你</key>
<string>066</string>
</dict>
<dict>
<key>/NO</key>
<string>093</string>
</dict>
<dict>
<key>/OK</key>
<string>065</string>
</dict>
<dict>
<key>/爱情</key>
<string>039</string>
</dict>
<dict>
<key>/飞吻</key>
<string>048</string>
</dict>
<dict>
<key>/跳跳</key>
<string>094</string>
</dict>
<dict>
<key>/发抖</key>
<string>070</string>
</dict>
<dict>
<key>/怄火</key>
<string>095</string>
</dict>
<dict>
<key>/转圈</key>
<string>096</string>
</dict>
<dict>
<key>/磕头</key>
<string>097</string>
</dict>
<dict>
<key>/回头</key>
<string>098</string>
</dict>
<dict>
<key>/跳绳</key>
<string>099</string>
</dict>
<dict>
<key>/挥手</key>
<string>078</string>
</dict>
<dict>
<key>/激动</key>
<string>100</string>
</dict>
<dict>
<key>/街舞</key>
<string>101</string>
</dict>
<dict>
<key>/献吻</key>
<string>102</string>
</dict>
<dict>
<key>/左太极</key>
<string>103</string>
</dict>
<dict>
<key>/右太极</key>
<string>104</string>
</dict>
<dict>
<key>/双喜</key>
<string>107</string>
</dict>
<dict>
<key>/鞭炮</key>
<string>108</string>
</dict>
<dict>
<key>/灯笼</key>
<string>109</string>
</dict>
<dict>
<key>/发财</key>
<string>110</string>
</dict>
<dict>
<key>/K歌</key>
<string>111</string>
</dict>
<dict>
<key>/购物</key>
<string>112</string>
</dict>
<dict>
<key>/邮件</key>
<string>113</string>
</dict>
<dict>
<key>/帅</key>
<string>114</string>
</dict>
<dict>
<key>/喝彩</key>
<string>115</string>
</dict>
<dict>
<key>/祈祷</key>
<string>116</string>
</dict>
<dict>
<key>/爆筋</key>
<string>117</string>
</dict>
<dict>
<key>/棒棒糖</key>
<string>118</string>
</dict>
<dict>
<key>/喝奶</key>
<string>119</string>
</dict>
<dict>
<key>下面</key>
<string>120</string>
</dict>
<dict>
<key>/香蕉</key>
<string>121</string>
</dict>
<dict>
<key>/飞机</key>
<string>112</string>
</dict>
<dict>
<key>/开车</key>
<string>123</string>
</dict>
<dict>
<key>/左车头</key>
<string>124</string>
</dict>
<dict>
<key>/车厢</key>
<string>125</string>
</dict>
<dict>
<key>/右车头</key>
<string>126</string>
</dict>
<dict>
<key>/多云</key>
<string>127</string>
</dict>
<dict>
<key>/下雨</key>
<string>128</string>
</dict>
<dict>
<key>/钞票</key>
<string>129</string>
</dict>
<dict>
<key>/熊猫</key>
<string>130</string>
</dict>
<dict>
<key>/灯泡</key>
<string>131</string>
</dict>
<dict>
<key>/风车</key>
<string>132</string>
</dict>
<dict>
<key>/闹钟</key>
<string>133</string>
</dict>
<dict>
<key>/打伞</key>
<string>134</string>
</dict>
<dict>
<key>/彩球</key>
<string>135</string>
</dict>
<dict>
<key>/钻戒</key>
<string>136</string>
</dict>
<dict>
<key>/沙发</key>
<string>137</string>
</dict>
<dict>
<key>/纸巾</key>
<string>138</string>
</dict>
<dict>
<key>/药</key>
<string>139</string>
</dict>
<dict>
<key>/手枪</key>
<string>140</string>
</dict>
<dict>
<key>/青蛙</key>
<string>141</string>
</dict>
</array>
</plist>
================================================
FILE: Demo/YYTextDemo/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>zh</string>
<string>zh_TW</string>
<string>ja</string>
<string>fr</string>
<string>de</string>
<string>it</string>
<string>ko</string>
</array>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
================================================
FILE: Demo/YYTextDemo/NSBundle+YYAdd.h
================================================
//
// NSBundle+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 14/10/20.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
/**
Provides extensions for `NSBundle` to get resource by @2x or @3x...
Example: ico.png, ico@2x.png, ico@3x.png. Call scaledResource:@"ico" ofType:@"png"
on iPhone6 will return "ico@2x.png"'s path.
*/
@interface NSBundle (YYAdd)
/**
An array of NSNumber objects, shows the best order for path scale search.
e.g. iPhone3GS:@[@1,@2,@3] iPhone5:@[@2,@3,@1] iPhone6 Plus:@[@3,@2,@1]
*/
+ (NSArray *)preferredScales;
/**
Returns the full pathname for the resource file identified by the specified
name and extension and residing in a given bundle directory. It first search
the file with current screen's scale (such as @2x), then search from higher
scale to lower scale.
@param name The name of a resource file contained in the directory
specified by bundlePath.
@param ext If extension is an empty string or nil, the extension is
assumed not to exist and the file is the first file encountered that exactly matches name.
@param bundlePath The path of a top-level bundle directory. This must be a
valid path. For example, to specify the bundle directory for a Mac app, you
might specify the path /Applications/MyApp.app.
@return The full pathname for the resource file or nil if the file could not be
located. This method also returns nil if the bundle specified by the bundlePath
parameter does not exist or is not a readable directory.
*/
+ (NSString *)pathForScaledResource:(NSString *)name ofType:(NSString *)ext inDirectory:(NSString *)bundlePath;
/**
Returns the full pathname for the resource identified by the specified name and
file extension. It first search the file with current screen's scale (such as @2x),
then search from higher scale to lower scale.
@param name The name of the resource file. If name is an empty string or
nil, returns the first file encountered of the supplied type.
@param ext If extension is an empty string or nil, the extension is
assumed not to exist and the file is the first file encountered that exactly matches name.
@return The full pathname for the resource file or nil if the file could not be located.
*/
- (NSString *)pathForScaledResource:(NSString *)name ofType:(NSString *)ext;
/**
Returns the full pathname for the resource identified by the specified name and
file extension and located in the specified bundle subdirectory. It first search
the file with current screen's scale (such as @2x), then search from higher
scale to lower scale.
@param name The name of the resource file.
@param ext If extension is an empty string or nil, all the files in
subpath and its subdirectories are returned. If an extension is provided the
subdirectories are not searched.
@param subpath The name of the bundle subdirectory. Can be nil.
@return The full pathname for the resource file or nil if the file could not be located.
*/
- (NSString *)pathForScaledResource:(NSString *)name ofType:(NSString *)ext inDirectory:(NSString *)subpath;
@end
================================================
FILE: Demo/YYTextDemo/NSBundle+YYAdd.m
================================================
//
// NSBundle+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 14/10/20.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "NSBundle+YYAdd.h"
#import "NSString+YYAdd.h"
@implementation NSBundle (YYAdd)
+ (NSArray *)preferredScales {
static NSArray *scales;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
CGFloat screenScale = [UIScreen mainScreen].scale;
if (screenScale <= 1) {
scales = @[@1,@2,@3];
} else if (screenScale <= 2) {
scales = @[@2,@3,@1];
} else {
scales = @[@3,@2,@1];
}
});
return scales;
}
+ (NSString *)pathForScaledResource:(NSString *)name ofType:(NSString *)ext inDirectory:(NSString *)bundlePath {
if (name.length == 0) return nil;
if ([name hasSuffix:@"/"]) return [self pathForResource:name ofType:ext inDirectory:bundlePath];
NSString *path = nil;
NSArray *scales = [self preferredScales];
for (int s = 0; s < scales.count; s++) {
CGFloat scale = ((NSNumber *)scales[s]).floatValue;
NSString *scaledName = ext.length ? [name stringByAppendingNameScale:scale]
: [name stringByAppendingPathScale:scale];
path = [self pathForResource:scaledName ofType:ext inDirectory:bundlePath];
if (path) break;
}
return path;
}
- (NSString *)pathForScaledResource:(NSString *)name ofType:(NSString *)ext {
if (name.length == 0) return nil;
if ([name hasSuffix:@"/"]) return [self pathForResource:name ofType:ext];
NSString *path = nil;
NSArray *scales = [NSBundle preferredScales];
for (int s = 0; s < scales.count; s++) {
CGFloat scale = ((NSNumber *)scales[s]).floatValue;
NSString *scaledName = ext.length ? [name stringByAppendingNameScale:scale]
: [name stringByAppendingPathScale:scale];
path = [self pathForResource:scaledName ofType:ext];
if (path) break;
}
return path;
}
- (NSString *)pathForScaledResource:(NSString *)name ofType:(NSString *)ext inDirectory:(NSString *)subpath {
if (name.length == 0) return nil;
if ([name hasSuffix:@"/"]) return [self pathForResource:name ofType:ext];
NSString *path = nil;
NSArray *scales = [NSBundle preferredScales];
for (int s = 0; s < scales.count; s++) {
CGFloat scale = ((NSNumber *)scales[s]).floatValue;
NSString *scaledName = ext.length ? [name stringByAppendingNameScale:scale]
: [name stringByAppendingPathScale:scale];
path = [self pathForResource:scaledName ofType:ext inDirectory:subpath];
if (path) break;
}
return path;
}
@end
================================================
FILE: Demo/YYTextDemo/NSData+YYAdd.h
================================================
//
// NSData+YYAdd.h
// YYKit <https://github.com/ibireme/YYKit>
//
// Created by ibireme on 13/4/4.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
/**
Provide hash, encrypt, encode and some common method for `NSData`.
*/
@interface NSData (YYAdd)
#pragma mark - Others
///=============================================================================
/// @name Others
///=============================================================================
/**
Create data from the file in main bundle (similar to [UIImage imageNamed:]).
@param name The file name (in main bundle).
@return A new data create from the file.
*/
+ (NSData *)dataNamed:(NSString *)name;
@end
================================================
FILE: Demo/YYTextDemo/NSData+YYAdd.m
================================================
//
// NSData+YYAdd.m
// YYKit <https://github.com/ibireme/YYKit>
//
// Created by ibireme on 13/4/4.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "NSData+YYAdd.h"
@implementation NSData (YYAdd)
+ (NSData *)dataNamed:(NSString *)name {
NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:@""];
if (!path) return nil;
NSData *data = [NSData dataWithContentsOfFile:path];
return data;
}
@end
================================================
FILE: Demo/YYTextDemo/NSString+YYAdd.h
================================================
//
// NSString+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/3.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
/**
Provide hash, encrypt, encode and some common method for 'NSString'.
*/
@interface NSString (YYAdd)
#pragma mark - Drawing
///=============================================================================
/// @name Drawing
///=============================================================================
/**
Returns the size of the string if it were rendered with the specified constraints.
@param font The font to use for computing the string size.
@param size The maximum acceptable size for the string. This value is
used to calculate where line breaks and wrapping would occur.
@param lineBreakMode The line break options for computing the size of the string.
For a list of possible values, see NSLineBreakMode.
@return The width and height of the resulting string's bounding box.
These values may be rounded up to the nearest whole number.
*/
- (CGSize)sizeForFont:(UIFont *)font size:(CGSize)size mode:(NSLineBreakMode)lineBreakMode;
/**
Returns the width of the string if it were to be rendered with the specified
font on a single line.
@param font The font to use for computing the string width.
@return The width of the resulting string's bounding box. These values may be
rounded up to the nearest whole number.
*/
- (CGFloat)widthForFont:(UIFont *)font;
/**
Returns the height of the string if it were rendered with the specified constraints.
@param font The font to use for computing the string size.
@param width The maximum acceptable width for the string. This value is used
to calculate where line breaks and wrapping would occur.
@return The height of the resulting string's bounding box. These values
may be rounded up to the nearest whole number.
*/
- (CGFloat)heightForFont:(UIFont *)font width:(CGFloat)width;
#pragma mark - Regular Expression
///=============================================================================
/// @name Regular Expression
///=============================================================================
/**
Whether it can match the regular expression
@param regex The regular expression
@param options The matching options to report.
@return YES if can match the regex; otherwise, NO.
*/
- (BOOL)matchesRegex:(NSString *)regex options:(NSRegularExpressionOptions)options;
/**
Match the regular expression, and executes a given block using each object in the matches.
@param regex The regular expression
@param options The matching options to report.
@param block The block to apply to elements in the array of matches.
The block takes four arguments:
match: The match substring.
matchRange: The matching options.
stop: A reference to a Boolean value. The block can set the value
to YES to stop further processing of the array. The stop
argument is an out-only argument. You should only ever set
this Boolean to YES within the Block.
*/
- (void)enumerateRegexMatches:(NSString *)regex
options:(NSRegularExpressionOptions)options
usingBlock:(void (^)(NSString *match, NSRange matchRange, BOOL *stop))block;
/**
Returns a new string containing matching regular expressions replaced with the template string.
@param regex The regular expression
@param options The matching options to report.
@param replacement The substitution template used when replacing matching instances.
@return A string with matching regular expressions replaced by the template string.
*/
- (NSString *)stringByReplacingRegex:(NSString *)regex
options:(NSRegularExpressionOptions)options
withString:(NSString *)replacement;
#pragma mark - Emoji
///=============================================================================
/// @name Emoji
///=============================================================================
/**
Whether the receiver contains Apple Emoji (displayed in current version of iOS).
*/
- (BOOL)containsEmoji;
- (BOOL)containsEmojiForSystemVersion:(double)systemVersion;
#pragma mark - Utilities
///=============================================================================
/// @name Utilities
///=============================================================================
/**
Returns a new UUID NSString
e.g. "D1178E50-2A4D-4F1F-9BD3-F6AAB00E06B1"
*/
+ (NSString *)stringWithUUID;
/**
Returns a string containing the characters in a given UTF32Char.
@param char32 A UTF-32 character.
@return A new string, or nil if the character is invalid.
*/
+ (NSString *)stringWithUTF32Char:(UTF32Char)char32;
/**
Returns a string containing the characters in a given UTF32Char array.
@param char32 An array of UTF-32 character.
@param length The character count in array.
@return A new string, or nil if an error occurs.
*/
+ (NSString *)stringWithUTF32Chars:(const UTF32Char *)char32 length:(NSUInteger)length;
/**
Enumerates the unicode characters (UTF-32) in the specified range of the string.
@param range The range within the string to enumerate substrings.
@param block The block executed for the enumeration. The block takes four arguments:
char32: The unicode character.
range: The range in receiver. If the range.length is 1, the character is in BMP;
otherwise (range.length is 2) the character is in none-BMP Plane and stored
by a surrogate pair in the receiver.
stop: A reference to a Boolean value that the block can use to stop the enumeration
by setting *stop = YES; it should not touch *stop otherwise.
*/
- (void)enumerateUTF32CharInRange:(NSRange)range usingBlock:(void (^)(UTF32Char char32, NSRange range, BOOL *stop))block;
/**
Trim blank characters (space and newline) in head and tail.
@return the trimmed string.
*/
- (NSString *)stringByTrim;
/**
Add scale modifier to the file name (without path extension),
From @"name" to @"name@2x".
e.g.
<table>
<tr><th>Before </th><th>After(scale:2)</th></tr>
<tr><td>"icon" </td><td>"icon@2x" </td></tr>
<tr><td>"icon " </td><td>"icon @2x" </td></tr>
<tr><td>"icon.top" </td><td>"icon.top@2x" </td></tr>
<tr><td>"/p/name" </td><td>"/p/name@2x" </td></tr>
<tr><td>"/path/" </td><td>"/path/" </td></tr>
</table>
@param scale Resource scale.
@return String by add scale modifier, or just return if it's not end with file name.
*/
- (NSString *)stringByAppendingNameScale:(CGFloat)scale;
/**
Add scale modifier to the file path (with path extension),
From @"name.png" to @"name@2x.png".
e.g.
<table>
<tr><th>Before </th><th>After(scale:2)</th></tr>
<tr><td>"icon.png" </td><td>"icon@2x.png" </td></tr>
<tr><td>"icon..png"</td><td>"icon.@2x.png"</td></tr>
<tr><td>"icon" </td><td>"icon@2x" </td></tr>
<tr><td>"icon " </td><td>"icon @2x" </td></tr>
<tr><td>"icon." </td><td>"icon.@2x" </td></tr>
<tr><td>"/p/name" </td><td>"/p/name@2x" </td></tr>
<tr><td>"/path/" </td><td>"/path/" </td></tr>
</table>
@param scale Resource scale.
@return String by add scale modifier, or just return if it's not end with file name.
*/
- (NSString *)stringByAppendingPathScale:(CGFloat)scale;
/**
Return the path scale.
e.g.
<table>
<tr><th>Path </th><th>Scale </th></tr>
<tr><td>"icon.png" </td><td>1 </td></tr>
<tr><td>"icon@2x.png" </td><td>2 </td></tr>
<tr><td>"icon@2.5x.png" </td><td>2.5 </td></tr>
<tr><td>"icon@2x" </td><td>1 </td></tr>
<tr><td>"icon@2x..png" </td><td>1 </td></tr>
<tr><td>"icon@2x.png/" </td><td>1 </td></tr>
</table>
*/
- (CGFloat)pathScale;
/**
nil, @"", @" ", @"\n" will Returns NO; otherwise Returns YES.
*/
- (BOOL)isNotBlank;
/**
Returns YES if the target string is contained within the receiver.
@param string A string to test the the receiver.
@discussion Apple has implemented this method in iOS8.
*/
- (BOOL)containsString:(NSString *)string;
/**
Returns YES if the target CharacterSet is contained within the receiver.
@param set A character set to test the the receiver.
*/
- (BOOL)containsCharacterSet:(NSCharacterSet *)set;
/**
Returns NSMakeRange(0, self.length).
*/
- (NSRange)rangeOfAll;
/**
Create a string from the file in main bundle (similar to [UIImage imageNamed:]).
@param name The file name (in main bundle).
@return A new string create from the file in UTF-8 character encoding.
*/
+ (NSString *)stringNamed:(NSString *)name;
@end
================================================
FILE: Demo/YYTextDemo/NSString+YYAdd.m
================================================
//
// NSString+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/3.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "NSString+YYAdd.h"
@implementation NSString (YYAdd)
- (CGSize)sizeForFont:(UIFont *)font size:(CGSize)size mode:(NSLineBreakMode)lineBreakMode {
CGSize result;
if (!font) font = [UIFont systemFontOfSize:12];
if ([self respondsToSelector:@selector(boundingRectWithSize:options:attributes:context:)]) {
NSMutableDictionary *attr = [NSMutableDictionary new];
attr[NSFontAttributeName] = font;
if (lineBreakMode != NSLineBreakByWordWrapping) {
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.lineBreakMode = lineBreakMode;
attr[NSParagraphStyleAttributeName] = paragraphStyle;
}
CGRect rect = [self boundingRectWithSize:size
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
attributes:attr context:nil];
result = rect.size;
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
result = [self sizeWithFont:font constrainedToSize:size lineBreakMode:lineBreakMode];
#pragma clang diagnostic pop
}
return result;
}
- (CGFloat)widthForFont:(UIFont *)font {
CGSize size = [self sizeForFont:font size:CGSizeMake(HUGE, HUGE) mode:NSLineBreakByWordWrapping];
return size.width;
}
- (CGFloat)heightForFont:(UIFont *)font width:(CGFloat)width {
CGSize size = [self sizeForFont:font size:CGSizeMake(width, HUGE) mode:NSLineBreakByWordWrapping];
return size.height;
}
- (BOOL)matchesRegex:(NSString *)regex options:(NSRegularExpressionOptions)options {
NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:regex options:options error:NULL];
if (!pattern) return NO;
return ([pattern numberOfMatchesInString:self options:0 range:NSMakeRange(0, self.length)] > 0);
}
- (void)enumerateRegexMatches:(NSString *)regex
options:(NSRegularExpressionOptions)options
usingBlock:(void (^)(NSString *match, NSRange matchRange, BOOL *stop))block {
if (regex.length == 0 || !block) return;
NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:regex options:options error:nil];
if (!regex) return;
[pattern enumerateMatchesInString:self options:kNilOptions range:NSMakeRange(0, self.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
block([self substringWithRange:result.range], result.range, stop);
}];
}
- (NSString *)stringByReplacingRegex:(NSString *)regex
options:(NSRegularExpressionOptions)options
withString:(NSString *)replacement; {
NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:regex options:options error:nil];
if (!pattern) return self;
return [pattern stringByReplacingMatchesInString:self options:0 range:NSMakeRange(0, [self length]) withTemplate:replacement];
}
- (BOOL)containsEmoji {
return [self containsEmojiForSystemVersion:[UIDevice currentDevice].systemVersion.doubleValue];
}
- (BOOL)containsEmojiForSystemVersion:(double)systemVersion {
// If detected, it MUST contains emoji; otherwise it MAY not contains emoji.
static NSMutableCharacterSet *minSet8_3, *minSetOld;
// If detected, it may contains emoji; otherwise it MUST NOT contains emoji.
static NSMutableCharacterSet *maxSet;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
minSetOld = [NSMutableCharacterSet new];
[minSetOld addCharactersInString:@"u2139\u2194\u2195\u2196\u2197\u2198\u2199\u21a9\u21aa\u231a\u231b\u23e9\u23ea\u23eb\u23ec\u23f0\u23f3\u24c2\u25aa\u25ab\u25b6\u25c0\u25fb\u25fc\u25fd\u25fe\u2600\u2601\u260e\u2611\u2614\u2615\u261d\u261d\u263a\u2648\u2649\u264a\u264b\u264c\u264d\u264e\u264f\u2650\u2651\u2652\u2653\u2660\u2663\u2665\u2666\u2668\u267b\u267f\u2693\u26a0\u26a1\u26aa\u26ab\u26bd\u26be\u26c4\u26c5\u26ce\u26d4\u26ea\u26f2\u26f3\u26f5\u26fa\u26fd\u2702\u2705\u2708\u2709\u270a\u270b\u270c\u270c\u270f\u2712\u2714\u2716\u2728\u2733\u2734\u2744\u2747\u274c\u274e\u2753\u2754\u2755\u2757\u2764\u2795\u2796\u2797\u27a1\u27b0\u27bf\u2934\u2935\u2b05\u2b06\u2b07\u2b1b\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299\U0001f004\U0001f0cf\U0001f170\U0001f171\U0001f17e\U0001f17f\U0001f18e\U0001f191\U0001f192\U0001f193\U0001f194\U0001f195\U0001f196\U0001f197\U0001f198\U0001f199\U0001f19a\U0001f201\U0001f202\U0001f21a\U0001f22f\U0001f232\U0001f233\U0001f234\U0001f235\U0001f236\U0001f237\U0001f238\U0001f239\U0001f23a\U0001f250\U0001f251\U0001f300\U0001f301\U0001f302\U0001f303\U0001f304\U0001f305\U0001f306\U0001f307\U0001f308\U0001f309\U0001f30a\U0001f30b\U0001f30c\U0001f30d\U0001f30e\U0001f30f\U0001f310\U0001f311\U0001f312\U0001f313\U0001f314\U0001f315\U0001f316\U0001f317\U0001f318\U0001f319\U0001f31a\U0001f31b\U0001f31c\U0001f31d\U0001f31e\U0001f31f\U0001f320\U0001f330\U0001f331\U0001f332\U0001f333\U0001f334\U0001f335\U0001f337\U0001f338\U0001f339\U0001f33a\U0001f33b\U0001f33c\U0001f33d\U0001f33e\U0001f33f\U0001f340\U0001f341\U0001f342\U0001f343\U0001f344\U0001f345\U0001f346\U0001f347\U0001f348\U0001f349\U0001f34a\U0001f34b\U0001f34c\U0001f34d\U0001f34e\U0001f34f\U0001f350\U0001f351\U0001f352\U0001f353\U0001f354\U0001f355\U0001f356\U0001f357\U0001f358\U0001f359\U0001f35a\U0001f35b\U0001f35c\U0001f35d\U0001f35e\U0001f35f\U0001f360\U0001f361\U0001f362\U0001f363\U0001f364\U0001f365\U0001f366\U0001f367\U0001f368\U0001f369\U0001f36a\U0001f36b\U0001f36c\U0001f36d\U0001f36e\U0001f36f\U0001f370\U0001f371\U0001f372\U0001f373\U0001f374\U0001f375\U0001f376\U0001f377\U0001f378\U0001f379\U0001f37a\U0001f37b\U0001f37c\U0001f380\U0001f381\U0001f382\U0001f383\U0001f384\U0001f385\U0001f386\U0001f387\U0001f388\U0001f389\U0001f38a\U0001f38b\U0001f38c\U0001f38d\U0001f38e\U0001f38f\U0001f390\U0001f391\U0001f392\U0001f393\U0001f3a0\U0001f3a1\U0001f3a2\U0001f3a3\U0001f3a4\U0001f3a5\U0001f3a6\U0001f3a7\U0001f3a8\U0001f3a9\U0001f3aa\U0001f3ab\U0001f3ac\U0001f3ad\U0001f3ae\U0001f3af\U0001f3b0\U0001f3b1\U0001f3b2\U0001f3b3\U0001f3b4\U0001f3b5\U0001f3b6\U0001f3b7\U0001f3b8\U0001f3b9\U0001f3ba\U0001f3bb\U0001f3bc\U0001f3bd\U0001f3be\U0001f3bf\U0001f3c0\U0001f3c1\U0001f3c2\U0001f3c3\U0001f3c4\U0001f3c6\U0001f3c7\U0001f3c8\U0001f3c9\U0001f3ca\U0001f3e0\U0001f3e1\U0001f3e2\U0001f3e3\U0001f3e4\U0001f3e5\U0001f3e6\U0001f3e7\U0001f3e8\U0001f3e9\U0001f3ea\U0001f3eb\U0001f3ec\U0001f3ed\U0001f3ee\U0001f3ef\U0001f3f0\U0001f400\U0001f401\U0001f402\U0001f403\U0001f404\U0001f405\U0001f406\U0001f407\U0001f408\U0001f409\U0001f40a\U0001f40b\U0001f40c\U0001f40d\U0001f40e\U0001f40f\U0001f410\U0001f411\U0001f412\U0001f413\U0001f414\U0001f415\U0001f416\U0001f417\U0001f418\U0001f419\U0001f41a\U0001f41b\U0001f41c\U0001f41d\U0001f41e\U0001f41f\U0001f420\U0001f421\U0001f422\U0001f423\U0001f424\U0001f425\U0001f426\U0001f427\U0001f428\U0001f429\U0001f42a\U0001f42b\U0001f42c\U0001f42d\U0001f42e\U0001f42f\U0001f430\U0001f431\U0001f432\U0001f433\U0001f434\U0001f435\U0001f436\U0001f437\U0001f438\U0001f439\U0001f43a\U0001f43b\U0001f43c\U0001f43d\U0001f43e\U0001f440\U0001f442\U0001f443\U0001f444\U0001f445\U0001f446\U0001f447\U0001f448\U0001f449\U0001f44a\U0001f44b\U0001f44c\U0001f44d\U0001f44e\U0001f44f\U0001f450\U0001f451\U0001f452\U0001f453\U0001f454\U0001f455\U0001f456\U0001f457\U0001f458\U0001f459\U0001f45a\U0001f45b\U0001f45c\U0001f45d\U0001f45e\U0001f45f\U0001f460\U0001f461\U0001f462\U0001f463\U0001f464\U0001f465\U0001f466\U0001f467\U0001f468\U0001f469\U0001f46a\U0001f46b\U0001f46c\U0001f46d\U0001f46e\U0001f46f\U0001f470\U0001f471\U0001f472\U0001f473\U0001f474\U0001f475\U0001f476\U0001f477\U0001f478\U0001f479\U0001f47a\U0001f47b\U0001f47c\U0001f47d\U0001f47e\U0001f47f\U0001f480\U0001f481\U0001f482\U0001f483\U0001f484\U0001f485\U0001f486\U0001f487\U0001f488\U0001f489\U0001f48a\U0001f48b\U0001f48c\U0001f48d\U0001f48e\U0001f48f\U0001f490\U0001f491\U0001f492\U0001f493\U0001f494\U0001f495\U0001f496\U0001f497\U0001f498\U0001f499\U0001f49a\U0001f49b\U0001f49c\U0001f49d\U0001f49e\U0001f49f\U0001f4a0\U0001f4a1\U0001f4a2\U0001f4a3\U0001f4a4\U0001f4a5\U0001f4a6\U0001f4a7\U0001f4a8\U0001f4a9\U0001f4aa\U0001f4ab\U0001f4ac\U0001f4ad\U0001f4ae\U0001f4af\U0001f4b0\U0001f4b1\U0001f4b2\U0001f4b3\U0001f4b4\U0001f4b5\U0001f4b6\U0001f4b7\U0001f4b8\U0001f4b9\U0001f4ba\U0001f4bb\U0001f4bc\U0001f4bd\U0001f4be\U0001f4bf\U0001f4c0\U0001f4c1\U0001f4c2\U0001f4c3\U0001f4c4\U0001f4c5\U0001f4c6\U0001f4c7\U0001f4c8\U0001f4c9\U0001f4ca\U0001f4cb\U0001f4cc\U0001f4cd\U0001f4ce\U0001f4cf\U0001f4d0\U0001f4d1\U0001f4d2\U0001f4d3\U0001f4d4\U0001f4d5\U0001f4d6\U0001f4d7\U0001f4d8\U0001f4d9\U0001f4da\U0001f4db\U0001f4dc\U0001f4dd\U0001f4de\U0001f4df\U0001f4e0\U0001f4e1\U0001f4e2\U0001f4e3\U0001f4e4\U0001f4e5\U0001f4e6\U0001f4e7\U0001f4e8\U0001f4e9\U0001f4ea\U0001f4eb\U0001f4ec\U0001f4ed\U0001f4ee\U0001f4ef\U0001f4f0\U0001f4f1\U0001f4f2\U0001f4f3\U0001f4f4\U0001f4f5\U0001f4f6\U0001f4f7\U0001f4f9\U0001f4fa\U0001f4fb\U0001f4fc\U0001f500\U0001f501\U0001f502\U0001f503\U0001f504\U0001f505\U0001f506\U0001f507\U0001f508\U0001f509\U0001f50a\U0001f50b\U0001f50c\U0001f50d\U0001f50e\U0001f50f\U0001f510\U0001f511\U0001f512\U0001f513\U0001f514\U0001f515\U0001f516\U0001f517\U0001f518\U0001f519\U0001f51a\U0001f51b\U0001f51c\U0001f51d\U0001f51e\U0001f51f\U0001f520\U0001f521\U0001f522\U0001f523\U0001f524\U0001f525\U0001f526\U0001f527\U0001f528\U0001f529\U0001f52a\U0001f52b\U0001f52c\U0001f52d\U0001f52e\U0001f52f\U0001f530\U0001f531\U0001f532\U0001f533\U0001f534\U0001f535\U0001f536\U0001f537\U0001f538\U0001f539\U0001f53a\U0001f53b\U0001f53c\U0001f53d\U0001f550\U0001f551\U0001f552\U0001f553\U0001f554\U0001f555\U0001f556\U0001f557\U0001f558\U0001f559\U0001f55a\U0001f55b\U0001f55c\U0001f55d\U0001f55e\U0001f55f\U0001f560\U0001f561\U0001f562\U0001f563\U0001f564\U0001f565\U0001f566\U0001f567\U0001f5fb\U0001f5fc\U0001f5fd\U0001f5fe\U0001f5ff\U0001f600\U0001f601\U0001f602\U0001f603\U0001f604\U0001f605\U0001f606\U0001f607\U0001f608\U0001f609\U0001f60a\U0001f60b\U0001f60c\U0001f60d\U0001f60e\U0001f60f\U0001f610\U0001f611\U0001f612\U0001f613\U0001f614\U0001f615\U0001f616\U0001f617\U0001f618\U0001f619\U0001f61a\U0001f61b\U0001f61c\U0001f61d\U0001f61e\U0001f61f\U0001f620\U0001f621\U0001f622\U0001f623\U0001f624\U0001f625\U0001f626\U0001f627\U0001f628\U0001f629\U0001f62a\U0001f62b\U0001f62c\U0001f62d\U0001f62e\U0001f62f\U0001f630\U0001f631\U0001f632\U0001f633\U0001f634\U0001f635\U0001f636\U0001f637\U0001f638\U0001f639\U0001f63a\U0001f63b\U0001f63c\U0001f63d\U0001f63e\U0001f63f\U0001f640\U0001f645\U0001f646\U0001f647\U0001f648\U0001f649\U0001f64a\U0001f64b\U0001f64c\U0001f64d\U0001f64e\U0001f64f\U0001f680\U0001f681\U0001f682\U0001f683\U0001f684\U0001f685\U0001f686\U0001f687\U0001f688\U0001f689\U0001f68a\U0001f68b\U0001f68c\U0001f68d\U0001f68e\U0001f68f\U0001f690\U0001f691\U0001f692\U0001f693\U0001f694\U0001f695\U0001f696\U0001f697\U0001f698\U0001f699\U0001f69a\U0001f69b\U0001f69c\U0001f69d\U0001f69e\U0001f69f\U0001f6a0\U0001f6a1\U0001f6a2\U0001f6a3\U0001f6a4\U0001f6a5\U0001f6a6\U0001f6a7\U0001f6a8\U0001f6a9\U0001f6aa\U0001f6ab\U0001f6ac\U0001f6ad\U0001f6ae\U0001f6af\U0001f6b0\U0001f6b1\U0001f6b2\U0001f6b3\U0001f6b4\U0001f6b5\U0001f6b6\U0001f6b7\U0001f6b8\U0001f6b9\U0001f6ba\U0001f6bb\U0001f6bc\U0001f6bd\U0001f6be\U0001f6bf\U0001f6c0\U0001f6c1\U0001f6c2\U0001f6c3\U0001f6c4\U0001f6c5"];
maxSet = minSetOld.mutableCopy;
[maxSet addCharactersInRange:NSMakeRange(0x20e3, 1)]; // Combining Enclosing Keycap (multi-face emoji)
[maxSet addCharactersInRange:NSMakeRange(0xfe0f, 1)]; // Variation Selector
[maxSet addCharactersInRange:NSMakeRange(0x1f1e6, 26)]; // Regional Indicator Symbol Letter
minSet8_3 = minSetOld.mutableCopy;
[minSet8_3 addCharactersInRange:NSMakeRange(0x1f3fb, 5)]; // Color of skin
});
// 1. Most of string does not contains emoji, so test the maximum range of charset first.
if ([self rangeOfCharacterFromSet:maxSet].location == NSNotFound) return NO;
// 2. If the emoji can be detected by the minimum charset, return 'YES' directly.
if ([self rangeOfCharacterFromSet:((systemVersion < 8.3) ? minSetOld : minSet8_3)].location != NSNotFound) return YES;
// 3. The string contains some characters which may compose an emoji, but cannot detected with charset.
// Use a regular expression to detect the emoji. It's slower than using charset.
static NSRegularExpression *regexOld, *regex8_3, *regex9_0;
static dispatch_once_t onceTokenRegex;
dispatch_once(&onceTokenRegex, ^{
regexOld = [NSRegularExpression regularExpressionWithPattern:@"(©️|®️|™️|〰️|🇨🇳|🇩🇪|🇪🇸|🇫🇷|🇬🇧|🇮🇹|🇯🇵|🇰🇷|🇷🇺|🇺🇸)" options:kNilOptions error:nil];
regex8_3 = [NSRegularExpression regularExpressionWithPattern:@"(©️|®️|™️|〰️|🇦🇺|🇦🇹|🇧🇪|🇧🇷|🇨🇦|🇨🇱|🇨🇳|🇨🇴|🇩🇰|🇫🇮|🇫🇷|🇩🇪|🇭🇰|🇮🇳|🇮🇩|🇮🇪|🇮🇱|🇮🇹|🇯🇵|🇰🇷|🇲🇴|🇲🇾|🇲🇽|🇳🇱|🇳🇿|🇳🇴|🇵🇭|🇵🇱|🇵🇹|🇵🇷|🇷🇺|🇸🇦|🇸🇬|🇿🇦|🇪🇸|🇸🇪|🇨🇭|🇹🇷|🇬🇧|🇺🇸|🇦🇪|🇻🇳)" options:kNilOptions error:nil];
regex9_0 = [NSRegularExpression regularExpressionWithPattern:@"(©️|®️|™️|〰️|🇦🇫|🇦🇽|🇦🇱|🇩🇿|🇦🇸|🇦🇩|🇦🇴|🇦🇮|🇦🇶|🇦🇬|🇦🇷|🇦🇲|🇦🇼|🇦🇺|🇦🇹|🇦🇿|🇧🇸|🇧🇭|🇧🇩|🇧🇧|🇧🇾|🇧🇪|🇧🇿|🇧🇯|🇧🇲|🇧🇹|🇧🇴|🇧🇶|🇧🇦|🇧🇼|🇧🇻|🇧🇷|🇮🇴|🇻🇬|🇧🇳|🇧🇬|🇧🇫|🇧🇮|🇰🇭|🇨🇲|🇨🇦|🇨🇻|🇰🇾|🇨🇫|🇹🇩|🇨🇱|🇨🇳|🇨🇽|🇨🇨|🇨🇴|🇰🇲|🇨🇬|🇨🇩|🇨🇰|🇨🇷|🇨🇮|🇭🇷|🇨🇺|🇨🇼|🇨🇾|🇨🇿|🇩🇰|🇩🇯|🇩🇲|🇩🇴|🇪🇨|🇪🇬|🇸🇻|🇬🇶|🇪🇷|🇪🇪|🇪🇹|🇫🇰|🇫🇴|🇫🇯|🇫🇮|🇫🇷|🇬🇫|🇵🇫|🇹🇫|🇬🇦|🇬🇲|🇬🇪|🇩🇪|🇬🇭|🇬🇮|🇬🇷|🇬🇱|🇬🇩|🇬🇵|🇬🇺|🇬🇹|🇬🇬|🇬🇳|🇬🇼|🇬🇾|🇭🇹|🇭🇲|🇭🇳|🇭🇰|🇭🇺|🇮🇸|🇮🇳|🇮🇩|🇮🇷|🇮🇶|🇮🇪|🇮🇲|🇮🇱|🇮🇹|🇯🇲|🇯🇵|🇯🇪|🇯🇴|🇰🇿|🇰🇪|🇰🇮|🇰🇼|🇰🇬|🇱🇦|🇱🇻|🇱🇧|🇱🇸|🇱🇷|🇱🇾|🇱🇮|🇱🇹|🇱🇺|🇲🇴|🇲🇰|🇲🇬|🇲🇼|🇲🇾|🇲🇻|🇲🇱|🇲🇹|🇲🇭|🇲🇶|🇲🇷|🇲🇺|🇾🇹|🇲🇽|🇫🇲|🇲🇩|🇲🇨|🇲🇳|🇲🇪|🇲🇸|🇲🇦|🇲🇿|🇲🇲|🇳🇦|🇳🇷|🇳🇵|🇳🇱|🇳🇨|🇳🇿|🇳🇮|🇳🇪|🇳🇬|🇳🇺|🇳🇫|🇲🇵|🇰🇵|🇳🇴|🇴🇲|🇵🇰|🇵🇼|🇵🇸|🇵🇦|🇵🇬|🇵🇾|🇵🇪|🇵🇭|🇵🇳|🇵🇱|🇵🇹|🇵🇷|🇶🇦|🇷🇪|🇷🇴|🇷🇺|🇷🇼|🇧🇱|🇸🇭|🇰🇳|🇱🇨|🇲🇫|🇻🇨|🇼🇸|🇸🇲|🇸🇹|🇸🇦|🇸🇳|🇷🇸|🇸🇨|🇸🇱|🇸🇬|🇸🇰|🇸🇮|🇸🇧|🇸🇴|🇿🇦|🇬🇸|🇰🇷|🇸🇸|🇪🇸|🇱🇰|🇸🇩|🇸🇷|🇸🇯|🇸🇿|🇸🇪|🇨🇭|🇸🇾|🇹🇼|🇹🇯|🇹🇿|🇹🇭|🇹🇱|🇹🇬|🇹🇰|🇹🇴|🇹🇹|🇹🇳|🇹🇷|🇹🇲|🇹🇨|🇹🇻|🇺🇬|🇺🇦|🇦🇪|🇬🇧|🇺🇸|🇺🇲|🇻🇮|🇺🇾|🇺🇿|🇻🇺|🇻🇦|🇻🇪|🇻🇳|🇼🇫|🇪🇭|🇾🇪|🇿🇲|🇿🇼)" options:kNilOptions error:nil];
});
NSRange regexRange = [(systemVersion < 8.3 ? regexOld : systemVersion < 9.0 ? regex8_3 : regex9_0) rangeOfFirstMatchInString:self options:kNilOptions range:NSMakeRange(0, self.length)];
return regexRange.location != NSNotFound;
}
+ (NSString *)stringWithUUID {
CFUUIDRef uuid = CFUUIDCreate(NULL);
CFStringRef string = CFUUIDCreateString(NULL, uuid);
CFRelease(uuid);
return (__bridge_transfer NSString *)string;
}
+ (NSString *)stringWithUTF32Char:(UTF32Char)char32 {
char32 = NSSwapHostIntToLittle(char32);
return [[NSString alloc] initWithBytes:&char32 length:4 encoding:NSUTF32LittleEndianStringEncoding];
}
+ (NSString *)stringWithUTF32Chars:(const UTF32Char *)char32 length:(NSUInteger)length {
return [[NSString alloc] initWithBytes:(const void *)char32
length:length * 4
encoding:NSUTF32LittleEndianStringEncoding];
}
- (void)enumerateUTF32CharInRange:(NSRange)range usingBlock:(void (^)(UTF32Char char32, NSRange range, BOOL *stop))block {
NSString *str = self;
if (range.location != 0 || range.length != self.length) {
str = [self substringWithRange:range];
}
NSUInteger len = [str lengthOfBytesUsingEncoding:NSUTF32StringEncoding] / 4;
UTF32Char *char32 = (UTF32Char *)[str cStringUsingEncoding:NSUTF32LittleEndianStringEncoding];
if (len == 0 || char32 == NULL) return;
NSUInteger location = 0;
BOOL stop = NO;
NSRange subRange;
UTF32Char oneChar;
for (NSUInteger i = 0; i < len; i++) {
oneChar = char32[i];
subRange = NSMakeRange(location, oneChar > 0xFFFF ? 2 : 1);
block(oneChar, subRange, &stop);
if (stop) return;
location += subRange.length;
}
}
- (NSString *)stringByTrim {
NSCharacterSet *set = [NSCharacterSet whitespaceAndNewlineCharacterSet];
return [self stringByTrimmingCharactersInSet:set];
}
- (NSString *)stringByAppendingNameScale:(CGFloat)scale {
if (fabs(scale - 1) <= __FLT_EPSILON__ || self.length == 0 || [self hasSuffix:@"/"]) return self.copy;
return [self stringByAppendingFormat:@"@%@x", @(scale)];
}
- (NSString *)stringByAppendingPathScale:(CGFloat)scale {
if (fabs(scale - 1) <= __FLT_EPSILON__ || self.length == 0 || [self hasSuffix:@"/"]) return self.copy;
NSString *ext = self.pathExtension;
NSRange extRange = NSMakeRange(self.length - ext.length, 0);
if (ext.length > 0) extRange.location -= 1;
NSString *scaleStr = [NSString stringWithFormat:@"@%@x", @(scale)];
return [self stringByReplacingCharactersInRange:extRange withString:scaleStr];
}
- (CGFloat)pathScale {
if (self.length == 0 || [self hasSuffix:@"/"]) return 1;
NSString *name = self.stringByDeletingPathExtension;
__block CGFloat scale = 1;
[name enumerateRegexMatches:@"@[0-9]+\\.?[0-9]*x$" options:NSRegularExpressionAnchorsMatchLines usingBlock: ^(NSString *match, NSRange matchRange, BOOL *stop) {
scale = [match substringWithRange:NSMakeRange(1, match.length - 2)].doubleValue;
}];
return scale;
}
- (BOOL)isNotBlank {
NSCharacterSet *blank = [NSCharacterSet whitespaceAndNewlineCharacterSet];
for (NSInteger i = 0; i < self.length; ++i) {
unichar c = [self characterAtIndex:i];
if (![blank characterIsMember:c]) {
return YES;
}
}
return NO;
}
- (BOOL)containsString:(NSString *)string {
if (string == nil) return NO;
return [self rangeOfString:string].location != NSNotFound;
}
- (BOOL)containsCharacterSet:(NSCharacterSet *)set {
if (set == nil) return NO;
return [self rangeOfCharacterFromSet:set].location != NSNotFound;
}
- (NSRange)rangeOfAll {
return NSMakeRange(0, self.length);
}
+ (NSString *)stringNamed:(NSString *)name {
NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:@""];
NSString *str = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
if (!str) {
path = [[NSBundle mainBundle] pathForResource:name ofType:@"txt"];
str = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
}
return str;
}
@end
================================================
FILE: Demo/YYTextDemo/UIControl+YYAdd.h
================================================
//
// UIControl+YYAdd.h
// YYKit <https://github.com/ibireme/YYKit>
//
// Created by ibireme on 13/4/5.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
/**
Provides extensions for `UIControl`.
*/
@interface UIControl (YYAdd)
/**
Removes all targets and actions for a particular event (or events)
from an internal dispatch table.
*/
- (void)removeAllTargets;
/**
Adds or replaces a target and action for a particular event (or events)
to an internal dispatch table.
@param target The target object—that is, the object to which the
action message is sent. If this is nil, the responder
chain is searched for an object willing to respond to the
action message.
@param action A selector identifying an action message. It cannot be NULL.
@param controlEvents A bitmask specifying the control events for which the
action message is sent.
*/
- (void)setTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents;
/**
Adds a block for a particular event (or events) to an internal dispatch table.
It will cause a strong reference to @a block.
@param block The block which is invoked then the action message is
sent (cannot be nil). The block is retained.
@param controlEvents A bitmask specifying the control events for which the
action message is sent.
*/
- (void)addBlockForControlEvents:(UIControlEvents)controlEvents block:(void (^)(id sender))block;
/**
Adds or replaces a block for a particular event (or events) to an internal
dispatch table. It will cause a strong reference to @a block.
@param block The block which is invoked then the action message is
sent (cannot be nil). The block is retained.
@param controlEvents A bitmask specifying the control events for which the
action message is sent.
*/
- (void)setBlockForControlEvents:(UIControlEvents)controlEvents block:(void (^)(id sender))block;
/**
Removes all blocks for a particular event (or events) from an internal
dispatch table.
@param controlEvents A bitmask specifying the control events for which the
action message is sent.
*/
- (void)removeAllBlocksForControlEvents:(UIControlEvents)controlEvents;
@end
================================================
FILE: Demo/YYTextDemo/UIControl+YYAdd.m
================================================
//
// UIControl+YYAdd.m
// YYKit <https://github.com/ibireme/YYKit>
//
// Created by ibireme on 13/4/5.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "UIControl+YYAdd.h"
#import <objc/runtime.h>
static const int block_key;
@interface _YYUIControlBlockTarget : NSObject
@property (nonatomic, copy) void (^block)(id sender);
@property (nonatomic, assign) UIControlEvents events;
- (id)initWithBlock:(void (^)(id sender))block events:(UIControlEvents)events;
- (void)invoke:(id)sender;
@end
@implementation _YYUIControlBlockTarget
- (id)initWithBlock:(void (^)(id sender))block events:(UIControlEvents)events {
self = [super init];
if (self) {
_block = [block copy];
_events = events;
}
return self;
}
- (void)invoke:(id)sender {
if (_block) _block(sender);
}
@end
@implementation UIControl (YYAdd)
- (void)removeAllTargets {
[[self allTargets] enumerateObjectsUsingBlock: ^(id object, BOOL *stop) {
[self removeTarget:object
action:NULL
forControlEvents:UIControlEventAllEvents];
}];
}
- (void)setTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents {
NSSet *targets = [self allTargets];
for (id currentTarget in targets) {
NSArray *actions = [self actionsForTarget:currentTarget forControlEvent:controlEvents];
for (NSString *currentAction in actions) {
[self removeTarget:currentTarget action:NSSelectorFromString(currentAction)
forControlEvents:controlEvents];
}
}
[self addTarget:target action:action forControlEvents:controlEvents];
}
- (void)addBlockForControlEvents:(UIControlEvents)controlEvents
block:(void (^)(id sender))block {
_YYUIControlBlockTarget *target = [[_YYUIControlBlockTarget alloc]
initWithBlock:block events:controlEvents];
[self addTarget:target action:@selector(invoke:) forControlEvents:controlEvents];
NSMutableArray *targets = [self _yy_allUIControlBlockTargets];
[targets addObject:target];
}
- (void)setBlockForControlEvents:(UIControlEvents)controlEvents
block:(void (^)(id sender))block {
[self removeAllBlocksForControlEvents:controlEvents];
[self addBlockForControlEvents:controlEvents block:block];
}
- (void)removeAllBlocksForControlEvents:(UIControlEvents)controlEvents {
NSMutableArray *targets = [self _yy_allUIControlBlockTargets];
NSMutableArray *removes = [NSMutableArray array];
[targets enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
_YYUIControlBlockTarget *target = (_YYUIControlBlockTarget *)obj;
if (target.events == controlEvents) {
[removes addObject:target];
[self removeTarget:target
action:@selector(invoke:)
forControlEvents:controlEvents];
}
}];
[targets removeObjectsInArray:removes];
}
- (NSMutableArray *)_yy_allUIControlBlockTargets {
NSMutableArray *targets = objc_getAssociatedObject(self, &block_key);
if (!targets) {
targets = [NSMutableArray array];
objc_setAssociatedObject(self, &block_key, targets, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return targets;
}
@end
================================================
FILE: Demo/YYTextDemo/UIGestureRecognizer+YYAdd.h
================================================
//
// UIGestureRecognizer+YYAdd.h
// YYKit <https://github.com/ibireme/YYKit>
//
// Created by ibireme on 13/10/13.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
/**
Provides extensions for `UIGestureRecognizer`.
*/
@interface UIGestureRecognizer (YYAdd)
/**
Initializes an allocated gesture-recognizer object with a action block.
@param block An action block that to handle the gesture recognized by the
receiver. nil is invalid. It is retained by the gesture.
@return An initialized instance of a concrete UIGestureRecognizer subclass or
nil if an error occurred in the attempt to initialize the object.
*/
- (instancetype)initWithActionBlock:(void (^)(id sender))block;
/**
Adds an action block to a gesture-recognizer object. It is retained by the
gesture.
@param block A block invoked by the action message. nil is not a valid value.
*/
- (void)addActionBlock:(void (^)(id sender))block;
/**
Remove all action blocks.
*/
- (void)removeAllActionBlocks;
@end
================================================
FILE: Demo/YYTextDemo/UIGestureRecognizer+YYAdd.m
================================================
//
// UIGestureRecognizer+YYAdd.m
// YYKit <https://github.com/ibireme/YYKit>
//
// Created by ibireme on 13/10/13.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "UIGestureRecognizer+YYAdd.h"
#import <objc/runtime.h>
static const int block_key;
@interface _YYUIGestureRecognizerBlockTarget : NSObject
@property (nonatomic, copy) void (^block)(id sender);
- (id)initWithBlock:(void (^)(id sender))block;
- (void)invoke:(id)sender;
@end
@implementation _YYUIGestureRecognizerBlockTarget
- (id)initWithBlock:(void (^)(id sender))block{
self = [super init];
if (self) {
_block = [block copy];
}
return self;
}
- (void)invoke:(id)sender {
if (_block) _block(sender);
}
@end
@implementation UIGestureRecognizer (YYAdd)
- (instancetype)initWithActionBlock:(void (^)(id sender))block {
self = [self init];
[self addActionBlock:block];
return self;
}
- (void)addActionBlock:(void (^)(id sender))block {
_YYUIGestureRecognizerBlockTarget *target = [[_YYUIGestureRecognizerBlockTarget alloc] initWithBlock:block];
[self addTarget:target action:@selector(invoke:)];
NSMutableArray *targets = [self _yy_allUIGestureRecognizerBlockTargets];
[targets addObject:target];
}
- (void)removeAllActionBlocks{
NSMutableArray *targets = [self _yy_allUIGestureRecognizerBlockTargets];
[targets enumerateObjectsUsingBlock:^(id target, NSUInteger idx, BOOL *stop) {
[self removeTarget:target action:@selector(invoke:)];
}];
[targets removeAllObjects];
}
- (NSMutableArray *)_yy_allUIGestureRecognizerBlockTargets {
NSMutableArray *targets = objc_getAssociatedObject(self, &block_key);
if (!targets) {
targets = [NSMutableArray array];
objc_setAssociatedObject(self, &block_key, targets, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return targets;
}
@end
================================================
FILE: Demo/YYTextDemo/UIImage+YYWebImage.h
================================================
//
// UIImage+YYWebImage.h
// YYWebImage <https://github.com/ibireme/YYWebImage>
//
// Created by ibireme on 13/4/4.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
/**
Provide some commen method for `UIImage`.
Image process is based on CoreGraphic and vImage.
*/
@interface UIImage (YYWebImage)
#pragma mark - Create image
///=============================================================================
/// @name Create image
///=============================================================================
/**
Create an animated image with GIF data. After created, you can access
the images via property '.images'. If the data is not animated gif, this
function is same as [UIImage imageWithData:data scale:scale];
@discussion It has a better display performance, but costs more memory
(width * height * frames Bytes). It only suited to display small
gif such as animated emoticon. If you want to display large gif,
see `YYImage`.
@param data GIF data.
@param scale The scale factor
@return A new image created from GIF, or nil when an error occurs.
*/
+ (UIImage *)yy_imageWithSmallGIFData:(NSData *)data scale:(CGFloat)scale;
/**
Create and return a 1x1 point size image with the given color.
@param color The color.
*/
+ (UIImage *)yy_imageWithColor:(UIColor *)color;
/**
Create and return a pure color image with the given color and size.
@param color The color.
@param size New image's type.
*/
+ (UIImage *)yy_imageWithColor:(UIColor *)color size:(CGSize)size;
/**
Create and return an image with custom draw code.
@param size The image size.
@param drawBlock The draw block.
@return The new image.
*/
+ (UIImage *)yy_imageWithSize:(CGSize)size drawBlock:(void (^)(CGContextRef context))drawBlock;
#pragma mark - Image Info
///=============================================================================
/// @name Image Info
///=============================================================================
/**
Whether this image has alpha channel.
*/
- (BOOL)yy_hasAlphaChannel;
#pragma mark - Modify Image
///=============================================================================
/// @name Modify Image
///=============================================================================
/**
Draws the entire image in the specified rectangle, content changed with
the contentMode.
@discussion This method draws the entire image in the current graphics context,
respecting the image's orientation setting. In the default coordinate system,
images are situated down and to the right of the origin of the specified
rectangle. This method respects any transforms applied to the current graphics
context, however.
@param rect The rectangle in which to draw the image.
@param contentMode Draw content mode
@param clips A Boolean value that determines whether content are confined to the rect.
*/
- (void)yy_drawInRect:(CGRect)rect withContentMode:(UIViewContentMode)contentMode clipsToBounds:(BOOL)clips;
/**
Returns a new image which is scaled from this image.
The image will be stretched as needed.
@param size The new size to be scaled, values should be positive.
@return The new image with the given size.
*/
- (UIImage *)yy_imageByResizeToSize:(CGSize)size;
/**
Returns a new image which is scaled from this image.
The image content will be changed with thencontentMode.
@param size The new size to be scaled, values should be positive.
@param contentMode The content mode for image content.
@return The new image with the given size.
*/
- (UIImage *)yy_imageByResizeToSize:(CGSize)size contentMode:(UIViewContentMode)contentMode;
/**
Returns a new image which is cropped from this image.
@param rect Image's inner rect.
@return The new image, or nil if an error occurs.
*/
- (UIImage *)yy_imageByCropToRect:(CGRect)rect;
/**
Returns a new image which is edge inset from this image.
@param insets Inset (positive) for each of the edges, values can be negative to 'outset'.
@param color Extend edge's fill color, nil means clear color.
@return The new image, or nil if an error occurs.
*/
- (UIImage *)yy_imageByInsetEdge:(UIEdgeInsets)insets withColor:(UIColor *)color;
/**
Rounds a new image with a given corner size.
@param radius The radius of each corner oval. Values larger than half the
rectangle's width or height are clamped appropriately to half
the width or height.
*/
- (UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius;
/**
Rounds a new image with a given corner size.
@param radius The radius of each corner oval. Values larger than half the
rectangle's width or height are clamped appropriately to
half the width or height.
@param corners A bitmask value that identifies the corners that you want
rounded. You can use this parameter to round only a subset
of the corners of the rectangle.
@param borderWidth The inset border with clear color. Values larger than half
the rectangle's width or height are clamped appropriately
to half the width or height.
*/
- (UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius
corners:(UIRectCorner)corners
borderWidth:(CGFloat)borderWidth;
/**
Returns a new rotated image (relative to the center).
@param radians Rotated radians in counterclockwise.⟲
@param fitSize YES: new image's size is extend to fit all content.
NO: image's size will not change, content may be clipped.
*/
- (UIImage *)yy_imageByRotate:(CGFloat)radians fitSize:(BOOL)fitSize;
/**
Returns a new image rotated counterclockwise by a quarter‑turn (90°). ⤺
The width and height will be exchanged.
*/
- (UIImage *)yy_imageByRotateLeft90;
/**
Returns a new image rotated clockwise by a quarter‑turn (90°). ⤼
The width and height will be exchanged.
*/
- (UIImage *)yy_imageByRotateRight90;
/**
Returns a new image rotated 180° . ↻
*/
- (UIImage *)yy_imageByRotate180;
/**
Returns a vertically flipped image. ⥯
*/
- (UIImage *)yy_imageByFlipVertical;
/**
Returns a horizontally flipped image. ⇋
*/
- (UIImage *)yy_imageByFlipHorizontal;
#pragma mark - Image Effect
///=============================================================================
/// @name Image Effect
///=============================================================================
/**
Tint the image in alpha channel with the given color.
@param color The color.
*/
- (UIImage *)yy_imageByTintColor:(UIColor *)color;
/**
Returns a grayscaled image.
*/
- (UIImage *)yy_imageByGrayscale;
/**
Applies a blur effect to this image. Suitable for blur any content.
*/
- (UIImage *)yy_imageByBlurSoft;
/**
Applies a blur effect to this image. Suitable for blur any content except pure white.
(same as iOS Control Panel)
*/
- (UIImage *)yy_imageByBlurLight;
/**
Applies a blur effect to this image. Suitable for displaying black text.
(same as iOS Navigation Bar White)
*/
- (UIImage *)yy_imageByBlurExtraLight;
/**
Applies a blur effect to this image. Suitable for displaying white text.
(same as iOS Notification Center)
*/
- (UIImage *)yy_imageByBlurDark;
/**
Applies a blur and tint color to this image.
@param tintColor The tint color.
*/
- (UIImage *)yy_imageByBlurWithTint:(UIColor *)tintColor;
/**
Applies a blur, tint color, and saturation adjustment to this image,
optionally within the area specified by @a maskImage.
@param blurRadius The radius of the blur in points, 0 means no blur effect.
@param tintColor An optional UIColor object that is uniformly blended with
the result of the blur and saturation operations. The
alpha channel of this color determines how strong the
tint is. nil means no tint.
@param tintBlendMode The @a tintColor blend mode. Default is kCGBlendModeNormal (0).
@param saturation A value of 1.0 produces no change in the resulting image.
Values less than 1.0 will desaturation the resulting image
while values greater than 1.0 will have the opposite effect.
0 means gray scale.
@param maskImage If specified, @a inputImage is only modified in the area(s)
defined by this mask. This must be an image mask or it
must meet the requirements of the mask parameter of
CGContextClipToMask.
@return image with effect, or nil if an error occurs (e.g. no
enough memory).
*/
- (UIImage *)yy_imageByBlurRadius:(CGFloat)blurRadius
tintColor:(UIColor *)tintColor
tintMode:(CGBlendMode)tintBlendMode
saturation:(CGFloat)saturation
maskImage:(UIImage *)maskImage;
@end
================================================
FILE: Demo/YYTextDemo/UIImage+YYWebImage.m
================================================
//
// UIImage+YYWebImage.m
// YYWebImage <https://github.com/ibireme/YYWebImage>
//
// Created by ibireme on 13/4/4.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "UIImage+YYWebImage.h"
#import <ImageIO/ImageIO.h>
#import <Accelerate/Accelerate.h>
#import <objc/runtime.h>
/// Convert degrees to radians.
static inline CGFloat _DegreesToRadians(CGFloat degrees) {
return degrees * M_PI / 180;
}
/**
Resize rect to fit the size using a given contentMode.
@param rect The draw rect
@param size The content size
@param mode The content mode
@return A resized rect for the given content mode.
@discussion UIViewContentModeRedraw is same as UIViewContentModeScaleToFill.
*/
static CGRect _YYCGRectFitWithContentMode(CGRect rect, CGSize size, UIViewContentMode mode) {
rect = CGRectStandardize(rect);
size.width = size.width < 0 ? -size.width : size.width;
size.height = size.height < 0 ? -size.height : size.height;
CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
switch (mode) {
case UIViewContentModeScaleAspectFit:
case UIViewContentModeScaleAspectFill: {
if (rect.size.width < 0.01 || rect.size.height < 0.01 ||
size.width < 0.01 || size.height < 0.01) {
rect.origin = center;
rect.size = CGSizeZero;
} else {
CGFloat scale;
if (size.width / size.height < rect.size.width / rect.size.height &&
mode == UIViewContentModeScaleAspectFit) {
scale = rect.size.height / size.height;
} else {
scale = rect.size.width / size.width;
}
size.width *= scale;
size.height *= scale;
rect.size = size;
rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5);
}
} break;
case UIViewContentModeCenter: {
rect.size = size;
rect.origin = CGPointMake(center.x - size.width * 0.5, center.y - size.height * 0.5);
} break;
case UIViewContentModeTop: {
rect.origin.x = center.x - size.width * 0.5;
rect.size = size;
} break;
case UIViewContentModeBottom: {
rect.origin.x = center.x - size.width * 0.5;
rect.origin.y += rect.size.height - size.height;
rect.size = size;
} break;
case UIViewContentModeLeft: {
rect.origin.y = center.y - size.height * 0.5;
rect.size = size;
} break;
case UIViewContentModeRight: {
rect.origin.y = center.y - size.height * 0.5;
rect.origin.x += rect.size.width - size.width;
rect.size = size;
} break;
case UIViewContentModeTopLeft: {
rect.size = size;
} break;
case UIViewContentModeTopRight: {
rect.origin.x += rect.size.width - size.width;
rect.size = size;
} break;
case UIViewContentModeBottomLeft: {
rect.origin.y += rect.size.height - size.height;
rect.size = size;
} break;
case UIViewContentModeBottomRight: {
rect.origin.x += rect.size.width - size.width;
rect.origin.y += rect.size.height - size.height;
rect.size = size;
} break;
case UIViewContentModeScaleToFill:
case UIViewContentModeRedraw:
default: {
rect = rect;
}
}
return rect;
}
static NSTimeInterval _yy_CGImageSourceGetGIFFrameDelayAtIndex(CGImageSourceRef source, size_t index) {
NSTimeInterval delay = 0;
CFDictionaryRef dic = CGImageSourceCopyPropertiesAtIndex(source, index, NULL);
if (dic) {
CFDictionaryRef dicGIF = CFDictionaryGetValue(dic, kCGImagePropertyGIFDictionary);
if (dicGIF) {
NSNumber *num = CFDictionaryGetValue(dicGIF, kCGImagePropertyGIFUnclampedDelayTime);
if (num.doubleValue <= __FLT_EPSILON__) {
num = CFDictionaryGetValue(dicGIF, kCGImagePropertyGIFDelayTime);
}
delay = num.doubleValue;
}
CFRelease(dic);
}
// http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser-compatibility
if (delay < 0.02) delay = 0.1;
return delay;
}
@implementation UIImage (YYWebImage)
+ (UIImage *)yy_imageWithSmallGIFData:(NSData *)data scale:(CGFloat)scale {
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFTypeRef)(data), NULL);
if (!source) return nil;
size_t count = CGImageSourceGetCount(source);
if (count <= 1) {
CFRelease(source);
return [self.class imageWithData:data scale:scale];
}
NSUInteger frames[count];
double oneFrameTime = 1 / 50.0; // 50 fps
NSTimeInterval totalTime = 0;
NSUInteger totalFrame = 0;
NSUInteger gcdFrame = 0;
for (size_t i = 0; i < count; i++) {
NSTimeInterval delay = _yy_CGImageSourceGetGIFFrameDelayAtIndex(source, i);
totalTime += delay;
NSInteger frame = lrint(delay / oneFrameTime);
if (frame < 1) frame = 1;
frames[i] = frame;
totalFrame += frames[i];
if (i == 0) gcdFrame = frames[i];
else {
NSUInteger frame = frames[i], tmp;
if (frame < gcdFrame) {
tmp = frame; frame = gcdFrame; gcdFrame = tmp;
}
while (true) {
tmp = frame % gcdFrame;
if (tmp == 0) break;
frame = gcdFrame;
gcdFrame = tmp;
}
}
}
NSMutableArray *array = [NSMutableArray new];
for (size_t i = 0; i < count; i++) {
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL);
if (!imageRef) {
CFRelease(source);
return nil;
}
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
if (width == 0 || height == 0) {
CFRelease(source);
CFRelease(imageRef);
return nil;
}
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask;
BOOL hasAlpha = NO;
if (alphaInfo == kCGImageAlphaPremultipliedLast ||
alphaInfo == kCGImageAlphaPremultipliedFirst ||
alphaInfo == kCGImageAlphaLast ||
alphaInfo == kCGImageAlphaFirst) {
hasAlpha = YES;
}
// BGRA8888 (premultiplied) or BGRX8888
// same as UIGraphicsBeginImageContext() and -[UIView drawRect:]
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, bitmapInfo);
CGColorSpaceRelease(space);
if (!context) {
CFRelease(source);
CFRelease(imageRef);
return nil;
}
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decode
CGImageRef decoded = CGBitmapContextCreateImage(context);
CFRelease(context);
if (!decoded) {
CFRelease(source);
CFRelease(imageRef);
return nil;
}
UIImage *image = image = [UIImage imageWithCGImage:decoded scale:scale orientation:UIImageOrientationUp];
CGImageRelease(imageRef);
CGImageRelease(decoded);
if (!image) {
CFRelease(source);
return nil;
}
for (size_t j = 0, max = frames[i] / gcdFrame; j < max; j++) {
[array addObject:image];
}
}
CFRelease(source);
UIImage *image = [self.class animatedImageWithImages:array duration:totalTime];
return image;
}
+ (UIImage *)yy_imageWithColor:(UIColor *)color {
return [self yy_imageWithColor:color size:CGSizeMake(1, 1)];
}
+ (UIImage *)yy_imageWithColor:(UIColor *)color size:(CGSize)size {
if (!color || size.width <= 0 || size.height <= 0) return nil;
CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
+ (UIImage *)yy_imageWithSize:(CGSize)size drawBlock:(void (^)(CGContextRef context))drawBlock {
if (!drawBlock) return nil;
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
if (!context) return nil;
drawBlock(context);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (BOOL)yy_hasAlphaChannel {
if (self.CGImage == NULL) return NO;
CGImageAlphaInfo alpha = CGImageGetAlphaInfo(self.CGImage) & kCGBitmapAlphaInfoMask;
return (alpha == kCGImageAlphaFirst ||
alpha == kCGImageAlphaLast ||
alpha == kCGImageAlphaPremultipliedFirst ||
alpha == kCGImageAlphaPremultipliedLast);
}
- (void)yy_drawInRect:(CGRect)rect withContentMode:(UIViewContentMode)contentMode clipsToBounds:(BOOL)clips{
CGRect drawRect = _YYCGRectFitWithContentMode(rect, self.size, contentMode);
if (drawRect.size.width == 0 || drawRect.size.height == 0) return;
if (clips) {
CGContextRef context = UIGraphicsGetCurrentContext();
if (context) {
CGContextSaveGState(context);
CGContextAddRect(context, rect);
CGContextClip(context);
[self drawInRect:drawRect];
CGContextRestoreGState(context);
}
} else {
[self drawInRect:drawRect];
}
}
- (UIImage *)yy_imageByResizeToSize:(CGSize)size {
if (size.width <= 0 || size.height <= 0) return nil;
UIGraphicsBeginImageContextWithOptions(size, NO, self.scale);
[self drawInRect:CGRectMake(0, 0, size.width, size.height)];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (UIImage *)yy_imageByResizeToSize:(CGSize)size contentMode:(UIViewContentMode)contentMode {
if (size.width <= 0 || size.height <= 0) return nil;
UIGraphicsBeginImageContextWithOptions(size, NO, self.scale);
[self yy_drawInRect:CGRectMake(0, 0, size.width, size.height) withContentMode:contentMode clipsToBounds:NO];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (UIImage *)yy_imageByCropToRect:(CGRect)rect {
rect.origin.x *= self.scale;
rect.origin.y *= self.scale;
rect.size.width *= self.scale;
rect.size.height *= self.scale;
if (rect.size.width <= 0 || rect.size.height <= 0) return nil;
CGImageRef imageRef = CGImageCreateWithImageInRect(self.CGImage, rect);
UIImage *image = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation];
CGImageRelease(imageRef);
return image;
}
- (UIImage *)yy_imageByInsetEdge:(UIEdgeInsets)insets withColor:(UIColor *)color {
CGSize size = self.size;
size.width -= insets.left + insets.right;
size.height -= insets.top + insets.bottom;
if (size.width <= 0 || size.height <= 0) return nil;
CGRect rect = CGRectMake(-insets.left, -insets.top, self.size.width, self.size.height);
UIGraphicsBeginImageContextWithOptions(size, NO, self.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
if (color) {
CGContextSetFillColorWithColor(context, color.CGColor);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0, 0, size.width, size.height));
CGPathAddRect(path, NULL, rect);
CGContextAddPath(context, path);
CGContextEOFillPath(context);
CGPathRelease(path);
}
[self drawInRect:rect];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius {
return [self yy_imageByRoundCornerRadius:radius corners:UIRectCornerAllCorners borderWidth:0];
}
- (UIImage *)yy_imageByRoundCornerRadius:(CGFloat)radius corners:(UIRectCorner)corners borderWidth:(CGFloat)borderWidth {
UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, 0, -rect.size.height);
CGFloat minSize = MIN(self.size.width, self.size.height);
if (borderWidth < minSize / 2) {
[[UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(radius, borderWidth)] addClip];
CGContextDrawImage(context, rect, self.CGImage);
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (UIImage *)yy_imageByRotate:(CGFloat)radians fitSize:(BOOL)fitSize {
size_t width = (size_t)CGImageGetWidth(self.CGImage);
size_t height = (size_t)CGImageGetHeight(self.CGImage);
CGRect newRect = CGRectApplyAffineTransform(CGRectMake(0., 0., width, height),
fitSize ? CGAffineTransformMakeRotation(radians) : CGAffineTransformIdentity);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL,
(size_t)newRect.size.width,
(size_t)newRect.size.height,
8,
(size_t)newRect.size.width * 4,
colorSpace,
kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace);
if (!context) return nil;
CGContextSetShouldAntialias(context, true);
CGContextSetAllowsAntialiasing(context, true);
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
CGContextTranslateCTM(context, +(newRect.size.width * 0.5), +(newRect.size.height * 0.5));
CGContextRotateCTM(context, radians);
CGContextDrawImage(context, CGRectMake(-(width * 0.5), -(height * 0.5), width, height), self.CGImage);
CGImageRef imgRef = CGBitmapContextCreateImage(context);
UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation];
CGImageRelease(imgRef);
CGContextRelease(context);
return img;
}
- (UIImage *)_yy_flipHorizontal:(BOOL)horizontal vertical:(BOOL)vertical {
if (!self.CGImage) return nil;
size_t width = (size_t)CGImageGetWidth(self.CGImage);
size_t height = (size_t)CGImageGetHeight(self.CGImage);
size_t bytesPerRow = width * 4;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace);
if (!context) return nil;
CGContextDrawImage(context, CGRectMake(0, 0, width, height), self.CGImage);
UInt8 *data = (UInt8 *)CGBitmapContextGetData(context);
if (!data) {
CGContextRelease(context);
return nil;
}
vImage_Buffer src = { data, height, width, bytesPerRow };
vImage_Buffer dest = { data, height, width, bytesPerRow };
if (vertical) {
vImageVerticalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill);
}
if (horizontal) {
vImageHorizontalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill);
}
CGImageRef imgRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation];
CGImageRelease(imgRef);
return img;
}
- (UIImage *)yy_imageByRotateLeft90 {
return [self yy_imageByRotate:_DegreesToRadians(90) fitSize:YES];
}
- (UIImage *)yy_imageByRotateRight90 {
return [self yy_imageByRotate:_DegreesToRadians(-90) fitSize:YES];
}
- (UIImage *)yy_imageByRotate180 {
return [self _yy_flipHorizontal:YES vertical:YES];
}
- (UIImage *)yy_imageByFlipVertical {
return [self _yy_flipHorizontal:NO vertical:YES];
}
- (UIImage *)yy_imageByFlipHorizontal {
return [self _yy_flipHorizontal:YES vertical:NO];
}
- (UIImage *)yy_imageByTintColor:(UIColor *)color {
UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
[color set];
UIRectFill(rect);
[self drawAtPoint:CGPointMake(0, 0) blendMode:kCGBlendModeDestinationIn alpha:1];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
- (UIImage *)yy_imageByGrayscale {
return [self yy_imageByBlurRadius:0 tintColor:nil tintMode:0 saturation:0 maskImage:nil];
}
- (UIImage *)yy_imageByBlurSoft {
return [self yy_imageByBlurRadius:60 tintColor:[UIColor colorWithWhite:0.84 alpha:0.36] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
}
- (UIImage *)yy_imageByBlurLight {
return [self yy_imageByBlurRadius:60 tintColor:[UIColor colorWithWhite:1.0 alpha:0.3] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
}
- (UIImage *)yy_imageByBlurExtraLight {
return [self yy_imageByBlurRadius:40 tintColor:[UIColor colorWithWhite:0.97 alpha:0.82] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
}
- (UIImage *)yy_imageByBlurDark {
return [self yy_imageByBlurRadius:40 tintColor:[UIColor colorWithWhite:0.11 alpha:0.73] tintMode:kCGBlendModeNormal saturation:1.8 maskImage:nil];
}
- (UIImage *)yy_imageByBlurWithTint:(UIColor *)tintColor {
const CGFloat EffectColorAlpha = 0.6;
UIColor *effectColor = tintColor;
size_t componentCount = CGColorGetNumberOfComponents(tintColor.CGColor);
if (componentCount == 2) {
CGFloat b;
if ([tintColor getWhite:&b alpha:NULL]) {
effectColor = [UIColor colorWithWhite:b alpha:EffectColorAlpha];
}
} else {
CGFloat r, g, b;
if ([tintColor getRed:&r green:&g blue:&b alpha:NULL]) {
effectColor = [UIColor colorWithRed:r green:g blue:b alpha:EffectColorAlpha];
}
}
return [self yy_imageByBlurRadius:20 tintColor:effectColor tintMode:kCGBlendModeNormal saturation:-1.0 maskImage:nil];
}
- (UIImage *)yy_imageByBlurRadius:(CGFloat)blurRadius
tintColor:(UIColor *)tintColor
tintMode:(CGBlendMode)tintBlendMode
saturation:(CGFloat)saturation
maskImage:(UIImage *)maskImage {
if (self.size.width < 1 || self.size.height < 1) {
NSLog(@"UIImage+YYAdd error: invalid size: (%.2f x %.2f). Both dimensions must be >= 1: %@", self.size.width, self.size.height, self);
return nil;
}
if (!self.CGImage) {
NSLog(@"UIImage+YYAdd error: inputImage must be backed by a CGImage: %@", self);
return nil;
}
if (maskImage && !maskImage.CGImage) {
NSLog(@"UIImage+YYAdd error: effectMaskImage must be backed by a CGImage: %@", maskImage);
return nil;
}
// iOS7 and above can use new func.
BOOL hasNewFunc = (long)vImageBuffer_InitWithCGImage != 0 && (long)vImageCreateCGImageFromBuffer != 0;
BOOL hasBlur = blurRadius > __FLT_EPSILON__;
BOOL hasSaturation = fabs(saturation - 1.0) > __FLT_EPSILON__;
CGSize size = self.size;
CGRect rect = { CGPointZero, size };
CGFloat scale = self.scale;
CGImageRef imageRef = self.CGImage;
BOOL opaque = NO;
if (!hasBlur && !hasSaturation) {
return [self _yy_mergeImageRef:imageRef tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque];
}
vImage_Buffer effect = { 0 }, scratch = { 0 };
vImage_Buffer *input = NULL, *output = NULL;
vImage_CGImageFormat format = {
.bitsPerComponent = 8,
.bitsPerPixel = 32,
.colorSpace = NULL,
.bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little, //requests a BGRA buffer.
.version = 0,
.decode = NULL,
.renderingIntent = kCGRenderingIntentDefault
};
if (hasNewFunc) {
vImage_Error err;
err = vImageBuffer_InitWithCGImage(&effect, &format, NULL, imageRef, kvImagePrintDiagnosticsToConsole);
if (err != kvImageNoError) {
NSLog(@"UIImage+YYAdd error: vImageBuffer_InitWithCGImage returned error code %zi for inputImage: %@", err, self);
return nil;
}
err = vImageBuffer_Init(&scratch, effect.height, effect.width, format.bitsPerPixel, kvImageNoFlags);
if (err != kvImageNoError) {
NSLog(@"UIImage+YYAdd error: vImageBuffer_Init returned error code %zi for inputImage: %@", err, self);
return nil;
}
} else {
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
CGContextRef effectCtx = UIGraphicsGetCurrentContext();
CGContextScaleCTM(effectCtx, 1.0, -1.0);
CGContextTranslateCTM(effectCtx, 0, -size.height);
CGContextDrawImage(effectCtx, rect, imageRef);
effect.data = CGBitmapContextGetData(effectCtx);
effect.width = CGBitmapContextGetWidth(effectCtx);
effect.height = CGBitmapContextGetHeight(effectCtx);
effect.rowBytes = CGBitmapContextGetBytesPerRow(effectCtx);
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
CGContextRef scratchCtx = UIGraphicsGetCurrentContext();
scratch.data = CGBitmapContextGetData(scratchCtx);
scratch.width = CGBitmapContextGetWidth(scratchCtx);
scratch.height = CGBitmapContextGetHeight(scratchCtx);
scratch.rowBytes = CGBitmapContextGetBytesPerRow(scratchCtx);
}
input = &effect;
output = &scratch;
if (hasBlur) {
// A description of how to compute the box kernel width from the Gaussian
// radius (aka standard deviation) appears in the SVG spec:
// http://www.w3.org/TR/SVG/filters.html#feGaussianBlurElement
//
// For larger values of 's' (s >= 2.0), an approximation can be used: Three
// successive box-blurs build a piece-wise quadratic convolution kernel, which
// approximates the Gaussian kernel to within roughly 3%.
//
// let d = floor(s * 3*sqrt(2*pi)/4 + 0.5)
//
// ... if d is odd, use three box-blurs of size 'd', centered on the output pixel.
//
CGFloat inputRadius = blurRadius * scale;
if (inputRadius - 2.0 < __FLT_EPSILON__) inputRadius = 2.0;
uint32_t radius = floor((inputRadius * 3.0 * sqrt(2 * M_PI) / 4 + 0.5) / 2);
radius |= 1; // force radius to be odd so that the three box-blur methodology works.
int iterations;
if (blurRadius * scale < 0.5) iterations = 1;
else if (blurRadius * scale < 1.5) iterations = 2;
else iterations = 3;
NSInteger tempSize = vImageBoxConvolve_ARGB8888(input, output, NULL, 0, 0, radius, radius, NULL, kvImageGetTempBufferSize | kvImageEdgeExtend);
void *temp = malloc(tempSize);
for (int i = 0; i < iterations; i++) {
vImageBoxConvolve_ARGB8888(input, output, temp, 0, 0, radius, radius, NULL, kvImageEdgeExtend);
// swap
vImage_Buffer *swap_tmp = input;
input = output;
output = swap_tmp;
}
free(temp);
}
if (hasSaturation) {
// These values appear in the W3C Filter Effects spec:
// https://dvcs.w3.org/hg/FXTF/raw-file/default/filters/Publish.html#grayscaleEquivalent
CGFloat s = saturation;
CGFloat matrixFloat[] = {
0.0722 + 0.9278 * s, 0.0722 - 0.0722 * s, 0.0722 - 0.0722 * s, 0,
0.7152 - 0.7152 * s, 0.7152 + 0.2848 * s, 0.7152 - 0.7152 * s, 0,
0.2126 - 0.2126 * s, 0.2126 - 0.2126 * s, 0.2126 + 0.7873 * s, 0,
0, 0, 0, 1,
};
const int32_t divisor = 256;
NSUInteger matrixSize = sizeof(matrixFloat) / sizeof(matrixFloat[0]);
int16_t matrix[matrixSize];
for (NSUInteger i = 0; i < matrixSize; ++i) {
matrix[i] = (int16_t)roundf(matrixFloat[i] * divisor);
}
vImageMatrixMultiply_ARGB8888(input, output, matrix, divisor, NULL, NULL, kvImageNoFlags);
// swap
vImage_Buffer *swap_tmp = input;
input = output;
output = swap_tmp;
}
UIImage *outputImage = nil;
if (hasNewFunc) {
CGImageRef effectCGImage = NULL;
effectCGImage = vImageCreateCGImageFromBuffer(input, &format, &_yy_cleanupBuffer, NULL, kvImageNoAllocate, NULL);
if (effectCGImage == NULL) {
effectCGImage = vImageCreateCGImageFromBuffer(input, &format, NULL, NULL, kvImageNoFlags, NULL);
free(input->data);
}
free(output->data);
outputImage = [self _yy_mergeImageRef:effectCGImage tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque];
CGImageRelease(effectCGImage);
} else {
CGImageRef effectCGImage;
UIImage *effectImage;
if (input != &effect) effectImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (input == &effect) effectImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
effectCGImage = effectImage.CGImage;
outputImage = [self _yy_mergeImageRef:effectCGImage tintColor:tintColor tintBlendMode:tintBlendMode maskImage:maskImage opaque:opaque];
}
return outputImage;
}
// Helper function to handle deferred cleanup of a buffer.
static void _yy_cleanupBuffer(void *userData, void *buf_data) {
free(buf_data);
}
// Helper function to add tint and mask.
- (UIImage *)_yy_mergeImageRef:(CGImageRef)effectCGImage
tintColor:(UIColor *)tintColor
tintBlendMode:(CGBlendMode)tintBlendMode
maskImage:(UIImage *)maskImage
opaque:(BOOL)opaque {
BOOL hasTint = tintColor != nil && CGColorGetAlpha(tintColor.CGColor) > __FLT_EPSILON__;
BOOL hasMask = maskImage != nil;
CGSize size = self.size;
CGRect rect = { CGPointZero, size };
CGFloat scale = self.scale;
if (!hasTint && !hasMask) {
return [UIImage imageWithCGImage:effectCGImage];
}
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0, -size.height);
if (hasMask) {
CGContextDrawImage(context, rect, self.CGImage);
CGContextSaveGState(context);
CGContextClipToMask(context, rect, maskImage.CGImage);
}
CGContextDrawImage(context, rect, effectCGImage);
if (hasTint) {
CGContextSaveGState(context);
CGContextSetBlendMode(context, tintBlendMode);
CGContextSetFillColorWithColor(context, tintColor.CGColor);
CGContextFillRect(context, rect);
CGContextRestoreGState(context);
}
if (hasMask) {
CGContextRestoreGState(context);
}
UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return outputImage;
}
@end
================================================
FILE: Demo/YYTextDemo/UIView+YYAdd.h
================================================
//
// UIView+YYAdd.h
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/3.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
/**
Provides extensions for `UIView`.
*/
@interface UIView (YYAdd)
/**
Shortcut to set the view.layer's shadow
@param color Shadow Color
@param offset Shadow offset
@param radius Shadow radius
*/
- (void)setLayerShadow:(UIColor*)color offset:(CGSize)offset radius:(CGFloat)radius;
/**
Remove all subviews.
@warning Never call this method inside your view's drawRect: method.
*/
- (void)removeAllSubviews;
/**
Returns the view's view controller (may be nil).
*/
@property (nonatomic, readonly) UIViewController *viewController;
@property (nonatomic) CGFloat left; ///< Shortcut for frame.origin.x.
@property (nonatomic) CGFloat top; ///< Shortcut for frame.origin.y
@property (nonatomic) CGFloat right; ///< Shortcut for frame.origin.x + frame.size.width
@property (nonatomic) CGFloat bottom; ///< Shortcut for frame.origin.y + frame.size.height
@property (nonatomic) CGFloat width; ///< Shortcut for frame.size.width.
@property (nonatomic) CGFloat height; ///< Shortcut for frame.size.height.
@property (nonatomic) CGFloat centerX; ///< Shortcut for center.x
@property (nonatomic) CGFloat centerY; ///< Shortcut for center.y
@property (nonatomic) CGPoint origin; ///< Shortcut for frame.origin.
@property (nonatomic) CGSize size; ///< Shortcut for frame.size.
@end
double YYDeviceSystemVersion();
#ifndef kSystemVersion
#define kSystemVersion YYDeviceSystemVersion()
#endif
#ifndef kiOS6Later
#define kiOS6Later (kSystemVersion >= 6)
#endif
#ifndef kiOS7Later
#define kiOS7Later (kSystemVersion >= 7)
#endif
#ifndef kiOS8Later
#define kiOS8Later (kSystemVersion >= 8)
#endif
#ifndef kiOS9Later
#define kiOS9Later (kSystemVersion >= 9)
#endif
CGSize YYDeviceScreenSize();
/// 屏幕宽度
#ifndef kScreenWidth
#define kScreenWidth YYDeviceScreenSize().width
#endif
/// 屏幕高度
#ifndef kScreenHeight
#define kScreenHeight YYDeviceScreenSize().height
#endif
/// 屏幕大小
#ifndef kScreenSize
#define kScreenSize YYDeviceScreenSize()
#endif
/// 屏幕Scale
#ifndef kScreenScale
#define kScreenScale [UIScreen mainScreen].scale
#endif
/*
Create UIColor with a hex string.
Example: UIColorHex(0xF0F), UIColorHex(66ccff), UIColorHex(#66CCFF88)
Valid format: #RGB #RGBA #RRGGBB #RRGGBBAA 0xRGB ...
The `#` or "0x" sign is not required.
*/
#ifndef UIColorHex
#define UIColorHex(_hex_) [UIColor colorWithHexString:((__bridge NSString *)CFSTR(#_hex_))]
#endif
@interface UIColor (YYAdd)
/**
Creates and returns a color object from hex string.
@discussion:
Valid format: #RGB #RGBA #RRGGBB #RRGGBBAA 0xRGB ...
The `#` or "0x" sign is not required.
The alpha will be set to 1.0 if there is no alpha component.
It will return nil when an error occurs in parsing.
Example: @"0xF0F", @"66ccff", @"#66CCFF88"
@param hexStr The hex string value for the new color.
@return An UIColor object from string, or nil if an error occurs.
*/
+ (UIColor *)colorWithHexString:(NSString *)hexStr;
/**
Creates and returns a color object by add new color.
@param add the color added
@param blendMode add color blend mode
*/
- (UIColor *)colorByAddColor:(UIColor *)add blendMode:(CGBlendMode)blendMode;
@end
================================================
FILE: Demo/YYTextDemo/UIView+YYAdd.m
================================================
//
// UIView+YYAdd.m
// YYCategories <https://github.com/ibireme/YYCategories>
//
// Created by ibireme on 13/4/3.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "UIView+YYAdd.h"
#import <QuartzCore/QuartzCore.h>
@implementation UIView (YYAdd)
- (void)setLayerShadow:(UIColor*)color offset:(CGSize)offset radius:(CGFloat)radius {
self.layer.shadowColor = color.CGColor;
self.layer.shadowOffset = offset;
self.layer.shadowRadius = radius;
self.layer.shadowOpacity = 1;
self.layer.shouldRasterize = YES;
self.layer.rasterizationScale = [UIScreen mainScreen].scale;
}
- (void)removeAllSubviews {
//[self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
while (self.subviews.count) {
[self.subviews.lastObject removeFromSuperview];
}
}
- (UIViewController *)viewController {
for (UIView *view = self; view; view = view.superview) {
UIResponder *nextResponder = [view nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]]) {
return (UIViewController *)nextResponder;
}
}
return nil;
}
- (CGFloat)left {
return self.frame.origin.x;
}
- (void)setLeft:(CGFloat)x {
CGRect frame = self.frame;
frame.origin.x = x;
self.frame = frame;
}
- (CGFloat)top {
return self.frame.origin.y;
}
- (void)setTop:(CGFloat)y {
CGRect frame = self.frame;
frame.origin.y = y;
self.frame = frame;
}
- (CGFloat)right {
return self.frame.origin.x + self.frame.size.width;
}
- (void)setRight:(CGFloat)right {
CGRect frame = self.frame;
frame.origin.x = right - frame.size.width;
self.frame = frame;
}
- (CGFloat)bottom {
return self.frame.origin.y + self.frame.size.height;
}
- (void)setBottom:(CGFloat)bottom {
CGRect frame = self.frame;
frame.origin.y = bottom - frame.size.height;
self.frame = frame;
}
- (CGFloat)width {
return self.frame.size.width;
}
- (void)setWidth:(CGFloat)width {
CGRect frame = self.frame;
frame.size.width = width;
self.frame = frame;
}
- (CGFloat)height {
return self.frame.size.height;
}
- (void)setHeight:(CGFloat)height {
CGRect frame = self.frame;
frame.size.height = height;
self.frame = frame;
}
- (CGFloat)centerX {
return self.center.x;
}
- (void)setCenterX:(CGFloat)centerX {
self.center = CGPointMake(centerX, self.center.y);
}
- (CGFloat)centerY {
return self.center.y;
}
- (void)setCenterY:(CGFloat)centerY {
self.center = CGPointMake(self.center.x, centerY);
}
- (CGPoint)origin {
return self.frame.origin;
}
- (void)setOrigin:(CGPoint)origin {
CGRect frame = self.frame;
frame.origin = origin;
self.frame = frame;
}
- (CGSize)size {
return self.frame.size;
}
- (void)setSize:(CGSize)size {
CGRect frame = self.frame;
frame.size = size;
self.frame = frame;
}
@end
double YYDeviceSystemVersion() {
static double version;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
version = [UIDevice currentDevice].systemVersion.doubleValue;
});
return version;
}
CGSize YYDeviceScreenSize() {
static CGSize size;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
size = [UIScreen mainScreen].bounds.size;
if (size.height <= size.width) {
CGFloat tmp = size.height;
size.height = size.width;
size.width = tmp;
}
});
return size;
}
@implementation UIColor (YYAdd)
static inline NSUInteger hexStrToInt(NSString *str) {
uint32_t result = 0;
sscanf([str UTF8String], "%X", &result);
return result;
}
static BOOL hexStrToRGBA(NSString *str,
CGFloat *r, CGFloat *g, CGFloat *b, CGFloat *a) {
str = [str uppercaseString];
if ([str hasPrefix:@"#"]) {
str = [str substringFromIndex:1];
} else if ([str hasPrefix:@"0X"]) {
str = [str substringFromIndex:2];
}
NSUInteger length = [str length];
// RGB RGBA RRGGBB RRGGBBAA
if (length != 3 && length != 4 && length != 6 && length != 8) {
return NO;
}
//RGB,RGBA,RRGGBB,RRGGBBAA
if (length < 5) {
*r = hexStrToInt([str substringWithRange:NSMakeRange(0, 1)]) / 255.0f;
*g = hexStrToInt([str substringWithRange:NSMakeRange(1, 1)]) / 255.0f;
*b = hexStrToInt([str substringWithRange:NSMakeRange(2, 1)]) / 255.0f;
if (length == 4) *a = hexStrToInt([str substringWithRange:NSMakeRange(3, 1)]) / 255.0f;
else *a = 1;
} else {
*r = hexStrToInt([str substringWithRange:NSMakeRange(0, 2)]) / 255.0f;
*g = hexStrToInt([str substringWithRange:NSMakeRange(2, 2)]) / 255.0f;
*b = hexStrToInt([str substringWithRange:NSMakeRange(4, 2)]) / 255.0f;
if (length == 8) *a = hexStrToInt([str substringWithRange:NSMakeRange(6, 2)]) / 255.0f;
else *a = 1;
}
return YES;
}
+ (instancetype)colorWithHexString:(NSString *)hexStr {
CGFloat r, g, b, a;
if (hexStrToRGBA(hexStr, &r, &g, &b, &a)) {
return [UIColor colorWithRed:r green:g blue:b alpha:a];
}
return nil;
}
- (UIColor *)colorByAddColor:(UIColor *)add blendMode:(CGBlendMode)blendMode {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big;
uint8_t pixel[4] = { 0 };
CGContextRef context = CGBitmapContextCreate(&pixel, 1, 1, 8, 4, colorSpace, bitmapInfo);
CGContextSetFillColorWithColor(context, self.CGColor);
CGContextFillRect(context, CGRectMake(0, 0, 1, 1));
CGContextSetBlendMode(context, blendMode);
CGContextSetFillColorWithColor(context, add.CGColor);
CGContextFillRect(context, CGRectMake(0, 0, 1, 1));
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
return [UIColor colorWithRed:pixel[0] / 255.0f green:pixel[1] / 255.0f blue:pixel[2] / 255.0f alpha:pixel[3] / 255.0f];
}
@end
================================================
FILE: Demo/YYTextDemo/ViewController.h
================================================
//
// ViewController.h
// YYTextDemo
//
// Created by ibireme on 15/10/17.
// Copyright © 2015年 ibireme. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface ViewController : UINavigationController
@end
================================================
FILE: Demo/YYTextDemo/ViewController.m
================================================
//
// ViewController.m
// YYTextDemo
//
// Created by ibireme on 15/10/17.
// Copyright © 2015年 ibireme. All rights reserved.
//
#import "ViewController.h"
#import "YYTextExample.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
YYTextExample *vc = [YYTextExample new];
[self pushViewController:vc animated:NO];
}
@end
================================================
FILE: Demo/YYTextDemo/YYFPSLabel.h
================================================
//
// YYFPSLabel.h
// YYKitExample
//
// Created by ibireme on 15/9/3.
// Copyright (c) 2015 ibireme. All rights reserved.
//
#import <UIKit/UIKit.h>
/**
Show Screen FPS...
The maximum fps in OSX/iOS Simulator is 60.00.
The maximum fps on iPhone is 59.97.
The maxmium fps on iPad is 60.0.
*/
@interface YYFPSLabel : UILabel
@end
================================================
FILE: Demo/YYTextDemo/YYFPSLabel.m
================================================
//
// YYFPSLabel.m
// YYKitExample
//
// Created by ibireme on 15/9/3.
// Copyright (c) 2015 ibireme. All rights reserved.
//
#import "YYFPSLabel.h"
//#import <YYKit/YYKit.h>
#import "YYText.h"
#import "YYWeakProxy.h"
#define kSize CGSizeMake(55, 20)
@implementation YYFPSLabel {
CADisplayLink *_link;
NSUInteger _count;
NSTimeInterval _lastTime;
UIFont *_font;
UIFont *_subFont;
NSTimeInterval _llll;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (frame.size.width == 0 && frame.size.height == 0) {
frame.size = kSize;
}
self = [super initWithFrame:frame];
self.layer.cornerRadius = 5;
self.clipsToBounds = YES;
self.textAlignment = NSTextAlignmentCenter;
self.userInteractionEnabled = NO;
self.backgroundColor = [UIColor colorWithWhite:0.000 alpha:0.700];
_font = [UIFont fontWithName:@"Menlo" size:14];
if (_font) {
_subFont = [UIFont fontWithName:@"Menlo" size:4];
} else {
_font = [UIFont fontWithName:@"Courier" size:14];
_subFont = [UIFont fontWithName:@"Courier" size:4];
}
_link = [CADisplayLink displayLinkWithTarget:[YYWeakProxy proxyWithTarget:self] selector:@selector(tick:)];
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
return self;
}
- (void)dealloc {
[_link invalidate];
}
- (CGSize)sizeThatFits:(CGSize)size {
return kSize;
}
- (void)tick:(CADisplayLink *)link {
if (_lastTime == 0) {
_lastTime = link.timestamp;
return;
}
_count++;
NSTimeInterval delta = link.timestamp - _lastTime;
if (delta < 1) return;
_lastTime = link.timestamp;
float fps = _count / delta;
_count = 0;
CGFloat progress = fps / 60.0;
UIColor *color = [UIColor colorWithHue:0.27 * (progress - 0.2) saturation:1 brightness:0.9 alpha:1];
NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:@"%d FPS",(int)round(fps)]];
[text yy_setColor:color range:NSMakeRange(0, text.length - 3)];
[text yy_setColor:[UIColor whiteColor] range:NSMakeRange(text.length - 3, 3)];
text.yy_font = _font;
[text yy_setFont:_subFont range:NSMakeRange(text.length - 4, 1)];
self.attributedText = text;
}
@end
================================================
FILE: Demo/YYTextDemo/YYGestureRecognizer.h
================================================
//
// YYGestureRecognizer.h
// YYKit <https://github.com/ibireme/YYKit>
//
// Created by ibireme on 14/10/26.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
/// State of the gesture
typedef NS_ENUM(NSUInteger, YYGestureRecognizerState) {
YYGestureRecognizerStateBegan, ///< gesture start
YYGestureRecognizerStateMoved, ///< gesture moved
YYGestureRecognizerStateEnded, ///< gesture end
YYGestureRecognizerStateCancelled, ///< gesture cancel
};
/**
A simple UIGestureRecognizer subclass for receive touch events.
*/
@interface YYGestureRecognizer : UIGestureRecognizer
@property (nonatomic, readonly) CGPoint startPoint; ///< start point
@property (nonatomic, readonly) CGPoint lastPoint; ///< last move point.
@property (nonatomic, readonly) CGPoint currentPoint; ///< current move point.
/// The action block invoked by every gesture event.
@property (nonatomic, copy) void (^action)(YYGestureRecognizer *gesture, YYGestureRecognizerState state);
/// Cancel the gesture for current touch.
- (void)cancel;
@end
================================================
FILE: Demo/YYTextDemo/YYGestureRecognizer.m
================================================
//
// YYGestureRecognizer.m
// YYKit <https://github.com/ibireme/YYKit>
//
// Created by ibireme on 14/10/26.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "YYGestureRecognizer.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
@implementation YYGestureRecognizer
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.state = UIGestureRecognizerStateBegan;
_startPoint = [(UITouch *)[touches anyObject] locationInView:self.view];
_lastPoint = _currentPoint;
_currentPoint = _startPoint;
if (_action) _action(self, YYGestureRecognizerStateBegan);
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = (UITouch *)[touches anyObject];
CGPoint currentPoint = [touch locationInView:self.view];
self.state = UIGestureRecognizerStateChanged;
_currentPoint = currentPoint;
if (_action) _action(self, YYGestureRecognizerStateMoved);
_lastPoint = _currentPoint;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
self.state = UIGestureRecognizerStateEnded;
if (_action) _action(self, YYGestureRecognizerStateEnded);
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
self.state = UIGestureRecognizerStateCancelled;
if (_action) _action(self, YYGestureRecognizerStateCancelled);
}
- (void)reset {
self.state = UIGestureRecognizerStatePossible;
}
- (void)cancel {
if (self.state == UIGestureRecognizerStateBegan || self.state == UIGestureRecognizerStateChanged) {
self.state = UIGestureRecognizerStateCancelled;
if (_action) _action(self, YYGestureRecognizerStateCancelled);
}
}
@end
================================================
FILE: Demo/YYTextDemo/YYImage/Animated image support.txt
================================================
You may import YYImage (https://github.com/ibireme/YYImage)
to support animated image copy and paste.
================================================
FILE: Demo/YYTextDemo/YYImage/YYAnimatedImageView.h
================================================
//
// YYAnimatedImageView.h
// YYImage <https://github.com/ibireme/YYImage>
//
// Created by ibireme on 14/10/19.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
An image view for displaying animated image.
@discussion It is a fully compatible `UIImageView` subclass.
If the `image` or `highlightedImage` property adopt to the `YYAnimatedImage` protocol,
then it can be used to play the multi-frame animation. The animation can also be
controlled with the UIImageView methods `-startAnimating`, `-stopAnimating` and `-isAnimating`.
This view request the frame data just in time. When the device has enough free memory,
this view may cache some or all future frames in an inner buffer for lower CPU cost.
Buffer size is dynamically adjusted based on the current state of the device memory.
Sample Code:
// ani@3x.gif
YYImage *image = [YYImage imageNamed:@"ani"];
YYAnimatedImageView *imageView = [YYAnimatedImageView alloc] initWithImage:image];
[view addSubView:imageView];
*/
@interface YYAnimatedImageView : UIImageView
/**
If the image has more than one frame, set this value to `YES` will automatically
play/stop the animation when the view become visible/invisible.
The default value is `YES`.
*/
@property (nonatomic) BOOL autoPlayAnimatedImage;
/**
Index of the currently displayed frame (index from 0).
Set a new value to this property will cause to display the new frame immediately.
If the new value is invalid, this method has no effect.
You can add an observer to this property to observe the playing status.
*/
@property (nonatomic) NSUInteger currentAnimatedImageIndex;
/**
Whether the image view is playing animation currently.
You can add an observer to this property to observe the playing status.
*/
@property (nonatomic, readonly) BOOL currentIsPlayingAnimation;
/**
The animation timer's runloop mode, default is `NSRunLoopCommonModes`.
Set this property to `NSDefaultRunLoopMode` will make the animation pause during
UIScrollView scrolling.
*/
@property (nonatomic, copy) NSString *runloopMode;
/**
The max size (in bytes) for inner frame buffer size, default is 0 (dynamically).
When the device has enough free memory, this view will request and decode some or
all future frame image into an inner buffer. If this property's value is 0, then
the max buffer size will be dynamically adjusted based on the current state of
the device free memory. Otherwise, the buffer size will be limited by this value.
When receive memory warning or app enter background, the buffer will be released
immediately, and may grow back at the right time.
*/
@property (nonatomic) NSUInteger maxBufferSize;
@end
/**
The YYAnimatedImage protocol declares the required methods for animated image
display with YYAnimatedImageView.
Subclass a UIImage and implement this protocol, so that instances of that class
can be set to YYAnimatedImageView.image or YYAnimatedImageView.highlightedImage
to display animation.
See `YYImage` and `YYFrameImage` for example.
*/
@protocol YYAnimatedImage <NSObject>
@required
/// Total animated frame count.
/// It the frame count is less than 1, then the methods below will be ignored.
- (NSUInteger)animatedImageFrameCount;
/// Animation loop count, 0 means infinite looping.
- (NSUInteger)animatedImageLoopCount;
/// Bytes per frame (in memory). It may used to optimize memory buffer size.
- (NSUInteger)animatedImageBytesPerFrame;
/// Returns the frame image from a specified index.
/// This method may be called on background thread.
/// @param index Frame index (zero based).
- (nullable UIImage *)animatedImageFrameAtIndex:(NSUInteger)index;
/// Returns the frames's duration from a specified index.
/// @param index Frame index (zero based).
- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index;
@optional
/// A rectangle in image coordinates defining the subrectangle of the image that
/// will be displayed. The rectangle should not outside the image's bounds.
/// It may used to display sprite animation with a single image (sprite sheet).
- (CGRect)animatedImageContentsRectAtIndex:(NSUInteger)index;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: Demo/YYTextDemo/YYImage/YYAnimatedImageView.m
================================================
//
// YYAnimatedImageView.m
// YYImage <https://github.com/ibireme/YYImage>
//
// Created by ibireme on 14/10/19.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "YYAnimatedImageView.h"
#import "YYImageCoder.h"
#import <pthread.h>
#import <mach/mach.h>
#define BUFFER_SIZE (10 * 1024 * 1024) // 10MB (minimum memory buffer size)
#define LOCK(...) dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER); \
__VA_ARGS__; \
dispatch_semaphore_signal(self->_lock);
#define LOCK_VIEW(...) dispatch_semaphore_wait(view->_lock, DISPATCH_TIME_FOREVER); \
__VA_ARGS__; \
dispatch_semaphore_signal(view->_lock);
static int64_t _YYDeviceMemoryTotal() {
int64_t mem = [[NSProcessInfo processInfo] physicalMemory];
if (mem < -1) mem = -1;
return mem;
}
static int64_t _YYDeviceMemoryFree() {
mach_port_t host_port = mach_host_self();
mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
vm_size_t page_size;
vm_statistics_data_t vm_stat;
kern_return_t kern;
kern = host_page_size(host_port, &page_size);
if (kern != KERN_SUCCESS) return -1;
kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size);
if (kern != KERN_SUCCESS) return -1;
return vm_stat.free_count * page_size;
}
/**
A proxy used to hold a weak object.
It can be used to avoid retain cycles, such as the target in NSTimer or CADisplayLink.
*/
@interface _YYImageWeakProxy : NSProxy
@property (nonatomic, weak, readonly) id target;
- (instancetype)initWithTarget:(id)target;
+ (instancetype)proxyWithTarget:(id)target;
@end
@implementation _YYImageWeakProxy
- (instancetype)initWithTarget:(id)target {
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target {
return [[_YYImageWeakProxy alloc] initWithTarget:target];
}
- (id)forwardingTargetForSelector:(SEL)selector {
return _target;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
void *null = NULL;
[invocation setReturnValue:&null];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [_target respondsToSelector:aSelector];
}
- (BOOL)isEqual:(id)object {
return [_target isEqual:object];
}
- (NSUInteger)hash {
return [_target hash];
}
- (Class)superclass {
return [_target superclass];
}
- (Class)class {
return [_target class];
}
- (BOOL)isKindOfClass:(Class)aClass {
return [_target isKindOfClass:aClass];
}
- (BOOL)isMemberOfClass:(Class)aClass {
return [_target isMemberOfClass:aClass];
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
return [_target conformsToProtocol:aProtocol];
}
- (BOOL)isProxy {
return YES;
}
- (NSString *)description {
return [_target description];
}
- (NSString *)debugDescription {
return [_target debugDescription];
}
@end
typedef NS_ENUM(NSUInteger, YYAnimatedImageType) {
YYAnimatedImageTypeNone = 0,
YYAnimatedImageTypeImage,
YYAnimatedImageTypeHighlightedImage,
YYAnimatedImageTypeImages,
YYAnimatedImageTypeHighlightedImages,
};
@interface YYAnimatedImageView() {
@package
UIImage <YYAnimatedImage> *_curAnimatedImage;
dispatch_once_t _onceToken;
dispatch_semaphore_t _lock; ///< lock for _buffer
NSOperationQueue *_requestQueue; ///< image request queue, serial
CADisplayLink *_link; ///< ticker for change frame
NSTimeInterval _time; ///< time after last frame
UIImage *_curFrame; ///< current frame to display
NSUInteger _curIndex; ///< current frame index (from 0)
NSUInteger _totalFrameCount; ///< total frame count
BOOL _loopEnd; ///< whether the loop is end.
NSUInteger _curLoop; ///< current loop count (from 0)
NSUInteger _totalLoop; ///< total loop count, 0 means infinity
NSMutableDictionary *_buffer; ///< frame buffer
BOOL _bufferMiss; ///< whether miss frame on last opportunity
NSUInteger _maxBufferCount; ///< maximum buffer count
NSInteger _incrBufferCount; ///< current allowed buffer count (will increase by step)
CGRect _curContentsRect;
BOOL _curImageHasContentsRect; ///< image has implementated "animatedImageContentsRectAtIndex:"
}
@property (nonatomic, readwrite) BOOL currentIsPlayingAnimation;
- (void)calcMaxBufferCount;
@end
/// An operation for image fetch
@interface _YYAnimatedImageViewFetchOperation : NSOperation
@property (nonatomic, weak) YYAnimatedImageView *view;
@property (nonatomic, assign) NSUInteger nextIndex;
@property (nonatomic, strong) UIImage <YYAnimatedImage> *curImage;
@end
@implementation _YYAnimatedImageViewFetchOperation
- (void)main {
__strong YYAnimatedImageView *view = _view;
if (!view) return;
if ([self isCancelled]) return;
view->_incrBufferCount++;
if (view->_incrBufferCount == 0) [view calcMaxBufferCount];
if (view->_incrBufferCount > (NSInteger)view->_maxBufferCount) {
view->_incrBufferCount = view->_maxBufferCount;
}
NSUInteger idx = _nextIndex;
NSUInteger max = view->_incrBufferCount < 1 ? 1 : view->_incrBufferCount;
NSUInteger total = view->_totalFrameCount;
view = nil;
for (int i = 0; i < max; i++, idx++) {
@autoreleasepool {
if (idx >= total) idx = 0;
if ([self isCancelled]) break;
__strong YYAnimatedImageView *view = _view;
if (!view) break;
LOCK_VIEW(BOOL miss = (view->_buffer[@(idx)] == nil));
if (miss) {
UIImage *img = [_curImage animatedImageFrameAtIndex:idx];
img = img.yy_imageByDecoded;
if ([self isCancelled]) break;
LOCK_VIEW(view->_buffer[@(idx)] = img ? img : [NSNull null]);
view = nil;
}
}
}
}
@end
@implementation YYAnimatedImageView
- (instancetype)init {
self = [super init];
_runloopMode = NSRunLoopCommonModes;
_autoPlayAnimatedImage = YES;
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
_runloopMode = NSRunLoopCommonModes;
_autoPlayAnimatedImage = YES;
return self;
}
- (instancetype)initWithImage:(UIImage *)image {
self = [super init];
_runloopMode = NSRunLoopCommonModes;
_autoPlayAnimatedImage = YES;
self.frame = (CGRect) {CGPointZero, image.size };
self.image = image;
return self;
}
- (instancetype)initWithImage:(UIImage *)image highlightedImage:(UIImage *)highlightedImage {
self = [super init];
_runloopMode = NSRunLoopCommonModes;
_autoPlayAnimatedImage = YES;
CGSize size = image ? image.size : highlightedImage.size;
self.frame = (CGRect) {CGPointZero, size };
self.image = image;
self.highlightedImage = highlightedImage;
return self;
}
// init the animated params.
- (void)resetAnimated {
dispatch_once(&_onceToken, ^{
_lock = dispatch_semaphore_create(1);
_buffer = [NSMutableDictionary new];
_requestQueue = [[NSOperationQueue alloc] init];
_requestQueue.maxConcurrentOperationCount = 1;
_link = [CADisplayLink displayLinkWithTarget:[_YYImageWeakProxy proxyWithTarget:self] selector:@selector(step:)];
if (_runloopMode) {
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:_runloopMode];
}
_link.paused = YES;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
});
[_requestQueue cancelAllOperations];
LOCK(
if (_buffer.count) {
NSMutableDictionary *holder = _buffer;
_buffer = [NSMutableDictionary new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Capture the dictionary to global queue,
// release these images in background to avoid blocking UI thread.
[holder class];
});
}
);
_link.paused = YES;
_time = 0;
if (_curIndex != 0) {
[self willChangeValueForKey:@"currentAnimatedImageIndex"];
_curIndex = 0;
[self didChangeValueForKey:@"currentAnimatedImageIndex"];
}
_curAnimatedImage = nil;
_curFrame = nil;
_curLoop = 0;
_totalLoop = 0;
_totalFrameCount = 1;
_loopEnd = NO;
_bufferMiss = NO;
_incrBufferCount = 0;
}
- (void)setImage:(UIImage *)image {
if (self.image == image) return;
[self setImage:image withType:YYAnimatedImageTypeImage];
}
- (void)setHighlightedImage:(UIImage *)highlightedImage {
if (self.highlightedImage == highlightedImage) return;
[self setImage:highlightedImage withType:YYAnimatedImageTypeHighlightedImage];
}
- (void)setAnimationImages:(NSArray *)animationImages {
if (self.animationImages == animationImages) return;
[self setImage:animationImages withType:YYAnimatedImageTypeImages];
}
- (void)setHighlightedAnimationImages:(NSArray *)highlightedAnimationImages {
if (self.highlightedAnimationImages == highlightedAnimationImages) return;
[self setImage:highlightedAnimationImages withType:YYAnimatedImageTypeHighlightedImages];
}
- (void)setHighlighted:(BOOL)highlighted {
[super setHighlighted:highlighted];
if (_link) [self resetAnimated];
[self imageChanged];
}
- (id)imageForType:(YYAnimatedImageType)type {
switch (type) {
case YYAnimatedImageTypeNone: return nil;
case YYAnimatedImageTypeImage: return self.image;
case YYAnimatedImageTypeHighlightedImage: return self.highlightedImage;
case YYAnimatedImageTypeImages: return self.animationImages;
case YYAnimatedImageTypeHighlightedImages: return self.highlightedAnimationImages;
}
return nil;
}
- (YYAnimatedImageType)currentImageType {
YYAnimatedImageType curType = YYAnimatedImageTypeNone;
if (self.highlighted) {
if (self.highlightedAnimationImages.count) curType = YYAnimatedImageTypeHighlightedImages;
else if (self.highlightedImage) curType = YYAnimatedImageTypeHighlightedImage;
}
if (curType == YYAnimatedImageTypeNone) {
if (self.animationImages.count) curType = YYAnimatedImageTypeImages;
else if (self.image) curType = YYAnimatedImageTypeImage;
}
return curType;
}
- (void)setImage:(id)image withType:(YYAnimatedImageType)type {
[self stopAnimating];
if (_link) [self resetAnimated];
_curFrame = nil;
switch (type) {
case YYAnimatedImageTypeNone: break;
case YYAnimatedImageTypeImage: super.image = image; break;
case YYAnimatedImageTypeHighlightedImage: super.highlightedImage = image; break;
case YYAnimatedImageTypeImages: super.animationImages = image; break;
case YYAnimatedImageTypeHighlightedImages: super.highlightedAnimationImages = image; break;
}
[self imageChanged];
}
- (void)imageChanged {
YYAnimatedImageType newType = [self currentImageType];
id newVisibleImage = [self imageForType:newType];
NSUInteger newImageFrameCount = 0;
BOOL hasContentsRect = NO;
if ([newVisibleImage isKindOfClass:[UIImage class]] &&
[newVisibleImage conformsToProtocol:@protocol(YYAnimatedImage)]) {
newImageFrameCount = ((UIImage<YYAnimatedImage> *) newVisibleImage).animatedImageFrameCount;
if (newImageFrameCount > 1) {
hasContentsRect = [((UIImage<YYAnimatedImage> *) newVisibleImage) respondsToSelector:@selector(animatedImageContentsRectAtIndex:)];
}
}
if (!hasContentsRect && _curImageHasContentsRect) {
if (!CGRectEqualToRect(self.layer.contentsRect, CGRectMake(0, 0, 1, 1)) ) {
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.layer.contentsRect = CGRectMake(0, 0, 1, 1);
[CATransaction commit];
}
}
_curImageHasContentsRect = hasContentsRect;
if (hasContentsRect) {
CGRect rect = [((UIImage<YYAnimatedImage> *) newVisibleImage) animatedImageContentsRectAtIndex:0];
[self setContentsRect:rect forImage:newVisibleImage];
}
if (newImageFrameCount > 1) {
[self resetAnimated];
_curAnimatedImage = newVisibleImage;
_curFrame = newVisibleImage;
_totalLoop = _curAnimatedImage.animatedImageLoopCount;
_totalFrameCount = _curAnimatedImage.animatedImageFrameCount;
[self calcMaxBufferCount];
}
[self setNeedsDisplay];
[self didMoved];
}
// dynamically adjust buffer size for current memory.
- (void)calcMaxBufferCount {
int64_t bytes = (int64_t)_curAnimatedImage.animatedImageBytesPerFrame;
if (bytes == 0) bytes = 1024;
int64_t total = _YYDeviceMemoryTotal();
int64_t free = _YYDeviceMemoryFree();
int64_t max = MIN(total * 0.2, free * 0.6);
max = MAX(max, BUFFER_SIZE);
if (_maxBufferSize) max = max > _maxBufferSize ? _maxBufferSize : max;
double maxBufferCount = (double)max / (double)bytes;
if (maxBufferCount < 1) maxBufferCount = 1;
else if (maxBufferCount > 512) maxBufferCount = 512;
_maxBufferCount = maxBufferCount;
}
- (void)dealloc {
[_requestQueue cancelAllOperations];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
[_link invalidate];
}
- (BOOL)isAnimating {
return self.currentIsPlayingAnimation;
}
- (void)stopAnimating {
[super stopAnimating];
[_requestQueue cancelAllOperations];
_link.paused = YES;
self.currentIsPlayingAnimation = NO;
}
- (void)startAnimating {
YYAnimatedImageType type = [self currentImageType];
if (type == YYAnimatedImageTypeImages || type == YYAnimatedImageTypeHighlightedImages) {
NSArray *images = [self imageForType:type];
if (images.count > 0) {
[super startAnimating];
self.currentIsPlayingAnimation = YES;
}
} else {
if (_curAnimatedImage && _link.paused) {
_curLoop = 0;
_loopEnd = NO;
_link.paused = NO;
self.currentIsPlayingAnimation = YES;
}
}
}
- (void)didReceiveMemoryWarning:(NSNotification *)notification {
[_requestQueue cancelAllOperations];
[_requestQueue addOperationWithBlock: ^{
_incrBufferCount = -60 - (int)(arc4random() % 120); // about 1~3 seconds to grow back..
NSNumber *next = @((_curIndex + 1) % _totalFrameCount);
LOCK(
NSArray * keys = _buffer.allKeys;
for (NSNumber * key in keys) {
if (![key isEqualToNumber:next]) { // keep the next frame for smoothly animation
[_buffer removeObjectForKey:key];
}
}
)//LOCK
}];
}
- (void)didEnterBackground:(NSNotification *)notification {
[_requestQueue cancelAllOperations];
NSNumber *next = @((_curIndex + 1) % _totalFrameCount);
LOCK(
NSArray * keys = _buffer.allKeys;
for (NSNumber * key in keys) {
if (![key isEqualToNumber:next]) { // keep the next frame for smoothly animation
[_buffer removeObjectForKey:key];
}
}
)//LOCK
}
- (void)step:(CADisplayLink *)link {
UIImage <YYAnimatedImage> *image = _curAnimatedImage;
NSMutableDictionary *buffer = _buffer;
UIImage *bufferedImage = nil;
NSUInteger nextIndex = (_curIndex + 1) % _totalFrameCount;
BOOL bufferIsFull = NO;
if (!image) return;
if (_loopEnd) { // view will keep in last frame
[self stopAnimating];
return;
}
NSTimeInterval delay = 0;
if (!_bufferMiss) {
_time += link.duration;
delay = [image animatedImageDurationAtIndex:_curIndex];
if (_time < delay) return;
_time -= delay;
if (nextIndex == 0) {
_curLoop++;
if (_curLoop >= _totalLoop && _totalLoop != 0) {
_loopEnd = YES;
[self stopAnimating];
[self.layer setNeedsDisplay]; // let system call `displayLayer:` before runloop sleep
return; // stop at last frame
}
}
delay = [image animatedImageDurationAtIndex:nextIndex];
if (_time > delay) _time = delay; // do not jump over frame
}
LOCK(
bufferedImage = buffer[@(nextIndex)];
if (bufferedImage) {
if ((int)_incrBufferCount < _totalFrameCount) {
[buffer removeObjectForKey:@(nextIndex)];
}
[self willChangeValueForKey:@"currentAnimatedImageIndex"];
_curIndex = nextIndex;
[self didChangeValueForKey:@"currentAnimatedImageIndex"];
_curFrame = bufferedImage == (id)[NSNull null] ? nil : bufferedImage;
if (_curImageHasContentsRect) {
_curContentsRect = [image animatedImageContentsRectAtIndex:_curIndex];
[self setContentsRect:_curContentsRect forImage:_curFrame];
}
nextIndex = (_curIndex + 1) % _totalFrameCount;
_bufferMiss = NO;
if (buffer.count == _totalFrameCount) {
bufferIsFull = YES;
}
} else {
_bufferMiss = YES;
}
)//LOCK
if (!_bufferMiss) {
[self.layer setNeedsDisplay]; // let system call `displayLayer:` before runloop sleep
}
if (!bufferIsFull && _requestQueue.operationCount == 0) { // if some work not finished, wait for next opportunity
_YYAnimatedImageViewFetchOperation *operation = [_YYAnimatedImageViewFetchOperation new];
operation.view = self;
operation.nextIndex = nextIndex;
operation.curImage = image;
[_requestQueue addOperation:operation];
}
}
- (void)displayLayer:(CALayer *)layer {
if (_curFrame) {
layer.contents = (__bridge id)_curFrame.CGImage;
}
}
- (void)setContentsRect:(CGRect)rect forImage:(UIImage *)image{
CGRect layerRect = CGRectMake(0, 0, 1, 1);
if (image) {
CGSize imageSize = image.size;
if (imageSize.width > 0.01 && imageSize.height > 0.01) {
layerRect.origin.x = rect.origin.x / imageSize.width;
layerRect.origin.y = rect.origin.y / imageSize.height;
layerRect.size.width = rect.size.width / imageSize.width;
layerRect.size.height = rect.size.height / imageSize.height;
layerRect = CGRectIntersection(layerRect, CGRectMake(0, 0, 1, 1));
if (CGRectIsNull(layerRect) || CGRectIsEmpty(layerRect)) {
layerRect = CGRectMake(0, 0, 1, 1);
}
}
}
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.layer.contentsRect = layerRect;
[CATransaction commit];
}
- (void)didMoved {
if (self.autoPlayAnimatedImage) {
if(self.superview && self.window) {
[self startAnimating];
} else {
[self stopAnimating];
}
}
}
- (void)didMoveToWindow {
[super didMoveToWindow];
[self didMoved];
}
- (void)didMoveToSuperview {
[super didMoveToSuperview];
[self didMoved];
}
- (void)setCurrentAnimatedImageIndex:(NSUInteger)currentAnimatedImageIndex {
if (!_curAnimatedImage) return;
if (currentAnimatedImageIndex >= _curAnimatedImage.animatedImageFrameCount) return;
if (_curIndex == currentAnimatedImageIndex) return;
void (^block)() = ^{
LOCK(
[_requestQueue cancelAllOperations];
[_buffer removeAllObjects];
[self willChangeValueForKey:@"currentAnimatedImageIndex"];
_curIndex = currentAnimatedImageIndex;
[self didChangeValueForKey:@"currentAnimatedImageIndex"];
_curFrame = [_curAnimatedImage animatedImageFrameAtIndex:_curIndex];
if (_curImageHasContentsRect) {
_curContentsRect = [_curAnimatedImage animatedImageContentsRectAtIndex:_curIndex];
}
_time = 0;
_loopEnd = NO;
_bufferMiss = NO;
[self.layer setNeedsDisplay];
)//LOCK
};
if (pthread_main_np()) {
block();
} else {
dispatch_async(dispatch_get_main_queue(), block);
}
}
- (NSUInteger)currentAnimatedImageIndex {
return _curIndex;
}
- (void)setRunloopMode:(NSString *)runloopMode {
if ([_runloopMode isEqual:runloopMode]) return;
if (_link) {
if (_runloopMode) {
[_link removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:_runloopMode];
}
if (runloopMode.length) {
[_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:runloopMode];
}
}
_runloopMode = runloopMode.copy;
}
#pragma mark - Overrice NSObject(NSKeyValueObservingCustomization)
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"currentAnimatedImageIndex"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
#pragma mark - NSCoding
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
_runloopMode = [aDecoder decodeObjectForKey:@"runloopMode"];
if (_runloopMode.length == 0) _runloopMode = NSRunLoopCommonModes;
if ([aDecoder containsValueForKey:@"autoPlayAnimatedImage"]) {
_autoPlayAnimatedImage = [aDecoder decodeBoolForKey:@"autoPlayAnimatedImage"];
} else {
_autoPlayAnimatedImage = YES;
}
UIImage *image = [aDecoder decodeObjectForKey:@"YYAnimatedImage"];
UIImage *highlightedImage = [aDecoder decodeObjectForKey:@"YYHighlightedAnimatedImage"];
if (image) {
self.image = image;
[self setImage:image withType:YYAnimatedImageTypeImage];
}
if (highlightedImage) {
self.highlightedImage = highlightedImage;
[self setImage:highlightedImage withType:YYAnimatedImageTypeHighlightedImage];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[super encodeWithCoder:aCoder];
[aCoder encodeObject:_runloopMode forKey:@"runloopMode"];
[aCoder encodeBool:_autoPlayAnimatedImage forKey:@"autoPlayAnimatedImage"];
BOOL ani, multi;
ani = [self.image conformsToProtocol:@protocol(YYAnimatedImage)];
multi = (ani && ((UIImage <YYAnimatedImage> *)self.image).animatedImageFrameCount > 1);
if (multi) [aCoder encodeObject:self.image forKey:@"YYAnimatedImage"];
ani = [self.highlightedImage conformsToProtocol:@protocol(YYAnimatedImage)];
multi = (ani && ((UIImage <YYAnimatedImage> *)self.highlightedImage).animatedImageFrameCount > 1);
if (multi) [aCoder encodeObject:self.highlightedImage forKey:@"YYHighlightedAnimatedImage"];
}
@end
================================================
FILE: Demo/YYTextDemo/YYImage/YYFrameImage.h
================================================
//
// YYFrameImage.h
// YYImage <https://github.com/ibireme/YYImage>
//
// Created by ibireme on 14/12/9.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#if __has_include(<YYImage/YYImage.h>)
#import <YYImage/YYAnimatedImageView.h>
#elif __has_include(<YYWebImage/YYImage.h>)
#import <YYWebImage/YYAnimatedImageView.h>
#else
#import "YYAnimatedImageView.h"
#endif
NS_ASSUME_NONNULL_BEGIN
/**
An image to display frame-based animation.
@discussion It is a fully compatible `UIImage` subclass.
It only support system image format such as png and jpeg.
The animation can be played by YYAnimatedImageView.
Sample Code:
NSArray *paths = @[@"/ani/frame1.png", @"/ani/frame2.png", @"/ani/frame3.png"];
NSArray *times = @[@0.1, @0.2, @0.1];
YYFrameImage *image = [YYFrameImage alloc] initWithImagePaths:paths frameDurations:times repeats:YES];
YYAnimatedImageView *imageView = [YYAnimatedImageView alloc] initWithImage:image];
[view addSubView:imageView];
*/
@interface YYFrameImage : UIImage <YYAnimatedImage>
/**
Create a frame animated image from files.
@param paths An array of NSString objects, contains the full or
partial path to each image file.
e.g. @[@"/ani/1.png",@"/ani/2.png",@"/ani/3.png"]
@param oneFrameDuration The duration (in seconds) per frame.
@param loopCount The animation loop count, 0 means infinite.
@return An initialized YYFrameImage object, or nil when an error occurs.
*/
- (nullable instancetype)initWithImagePaths:(NSArray<NSString *> *)paths
oneFrameDuration:(NSTimeInterval)oneFrameDuration
loopCount:(NSUInteger)loopCount;
/**
Create a frame animated image from files.
@param paths An array of NSString objects, contains the full or
partial path to each image file.
e.g. @[@"/ani/frame1.png",@"/ani/frame2.png",@"/ani/frame3.png"]
@param frameDurations An array of NSNumber objects, contains the duration (in seconds) per frame.
e.g. @[@0.1, @0.2, @0.3];
@param loopCount The animation loop count, 0 means infinite.
@return An initialized YYFrameImage object, or nil when an error occurs.
*/
- (nullable instancetype)initWithImagePaths:(NSArray<NSString *> *)paths
frameDurations:(NSArray<NSNumber *> *)frameDurations
loopCount:(NSUInteger)loopCount;
/**
Create a frame animated image from an array of data.
@param dataArray An array of NSData objects.
@param oneFrameDuration The duration (in seconds) per frame.
@param loopCount The animation loop count, 0 means infinite.
@return An initialized YYFrameImage object, or nil when an error occurs.
*/
- (nullable instancetype)initWithImageDataArray:(NSArray<NSData *> *)dataArray
oneFrameDuration:(NSTimeInterval)oneFrameDuration
loopCount:(NSUInteger)loopCount;
/**
Create a frame animated image from an array of data.
@param dataArray An array of NSData objects.
@param frameDurations An array of NSNumber objects, contains the duration (in seconds) per frame.
e.g. @[@0.1, @0.2, @0.3];
@param loopCount The animation loop count, 0 means infinite.
@return An initialized YYFrameImage object, or nil when an error occurs.
*/
- (nullable instancetype)initWithImageDataArray:(NSArray<NSData *> *)dataArray
frameDurations:(NSArray *)frameDurations
loopCount:(NSUInteger)loopCount;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: Demo/YYTextDemo/YYImage/YYFrameImage.m
================================================
//
// YYFrameImage.m
// YYImage <https://github.com/ibireme/YYImage>
//
// Created by ibireme on 14/12/9.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "YYFrameImage.h"
#import "YYImageCoder.h"
/**
Return the path scale.
e.g.
<table>
<tr><th>Path </th><th>Scale </th></tr>
<tr><td>"icon.png" </td><td>1 </td></tr>
<tr><td>"icon@2x.png" </td><td>2 </td></tr>
<tr><td>"icon@2.5x.png" </td><td>2.5 </td></tr>
<tr><td>"icon@2x" </td><td>1 </td></tr>
<tr><td>"icon@2x..png" </td><td>1 </td></tr>
<tr><td>"icon@2x.png/" </td><td>1 </td></tr>
</table>
*/
static CGFloat _NSStringPathScale(NSString *string) {
if (string.length == 0 || [string hasSuffix:@"/"]) return 1;
NSString *name = string.stringByDeletingPathExtension;
__block CGFloat scale = 1;
NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:@"@[0-9]+\\.?[0-9]*x$" options:NSRegularExpressionAnchorsMatchLines error:nil];
[pattern enumerateMatchesInString:name options:kNilOptions range:NSMakeRange(0, name.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
if (result.range.location >= 3) {
scale = [string substringWithRange:NSMakeRange(result.range.location + 1, result.range.length - 2)].doubleValue;
}
}];
return scale;
}
@implementation YYFrameImage {
NSUInteger _loopCount;
NSUInteger _oneFrameBytes;
NSArray *_imagePaths;
NSArray *_imageDatas;
NSArray *_frameDurations;
}
- (instancetype)initWithImagePaths:(NSArray *)paths oneFrameDuration:(NSTimeInterval)oneFrameDuration loopCount:(NSUInteger)loopCount {
NSMutableArray *durations = [NSMutableArray new];
for (int i = 0, max = (int)paths.count; i < max; i++) {
[durations addObject:@(oneFrameDuration)];
}
return [self initWithImagePaths:paths frameDurations:durations loopCount:loopCount];
}
- (instancetype)initWithImagePaths:(NSArray *)paths frameDurations:(NSArray *)frameDurations loopCount:(NSUInteger)loopCount {
if (paths.count == 0) return nil;
if (paths.count != frameDurations.count) return nil;
NSString *firstPath = paths[0];
NSData *firstData = [NSData dataWithContentsOfFile:firstPath];
CGFloat scale = _NSStringPathScale(firstPath);
UIImage *firstCG = [[[UIImage alloc] initWithData:firstData] yy_imageByDecoded];
self = [self initWithCGImage:firstCG.CGImage scale:scale orientation:UIImageOrientationUp];
if (!self) return nil;
long frameByte = CGImageGetBytesPerRow(firstCG.CGImage) * CGImageGetHeight(firstCG.CGImage);
_oneFrameBytes = (NSUInteger)frameByte;
_imagePaths = paths.copy;
_frameDurations = frameDurations.copy;
_loopCount = loopCount;
return self;
}
- (instancetype)initWithImageDataArray:(NSArray *)dataArray oneFrameDuration:(NSTimeInterval)oneFrameDuration loopCount:(NSUInteger)loopCount {
NSMutableArray *durations = [NSMutableArray new];
for (int i = 0, max = (int)dataArray.count; i < max; i++) {
[durations addObject:@(oneFrameDuration)];
}
return [self initWithImageDataArray:dataArray frameDurations:durations loopCount:loopCount];
}
- (instancetype)initWithImageDataArray:(NSArray *)dataArray frameDurations:(NSArray *)frameDurations loopCount:(NSUInteger)loopCount {
if (dataArray.count == 0) return nil;
if (dataArray.count != frameDurations.count) return nil;
NSData *firstData = dataArray[0];
CGFloat scale = [UIScreen mainScreen].scale;
UIImage *firstCG = [[[UIImage alloc] initWithData:firstData] yy_imageByDecoded];
self = [self initWithCGImage:firstCG.CGImage scale:scale orientation:UIImageOrientationUp];
if (!self) return nil;
long frameByte = CGImageGetBytesPerRow(firstCG.CGImage) * CGImageGetHeight(firstCG.CGImage);
_oneFrameBytes = (NSUInteger)frameByte;
_imageDatas = dataArray.copy;
_frameDurations = frameDurations.copy;
_loopCount = loopCount;
return self;
}
#pragma mark - YYAnimtedImage
- (NSUInteger)animatedImageFrameCount {
if (_imagePaths) {
return _imagePaths.count;
} else if (_imageDatas) {
return _imageDatas.count;
} else {
return 1;
}
}
- (NSUInteger)animatedImageLoopCount {
return _loopCount;
}
- (NSUInteger)animatedImageBytesPerFrame {
return _oneFrameBytes;
}
- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
if (_imagePaths) {
if (index >= _imagePaths.count) return nil;
NSString *path = _imagePaths[index];
CGFloat scale = _NSStringPathScale(path);
NSData *data = [NSData dataWithContentsOfFile:path];
return [[UIImage imageWithData:data scale:scale] yy_imageByDecoded];
} else if (_imageDatas) {
if (index >= _imageDatas.count) return nil;
NSData *data = _imageDatas[index];
return [[UIImage imageWithData:data scale:[UIScreen mainScreen].scale] yy_imageByDecoded];
} else {
return index == 0 ? self : nil;
}
}
- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
if (index >= _frameDurations.count) return 0;
NSNumber *num = _frameDurations[index];
return [num doubleValue];
}
@end
================================================
FILE: Demo/YYTextDemo/YYImage/YYImage.h
================================================
//
// YYImage.h
// YYImage <https://github.com/ibireme/YYImage>
//
// Created by ibireme on 14/10/20.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
#if __has_include(<YYImage/YYImage.h>)
FOUNDATION_EXPORT double YYImageVersionNumber;
FOUNDATION_EXPORT const unsigned char YYImageVersionString[];
#import <YYImage/YYFrameImage.h>
#import <YYImage/YYSpriteSheetImage.h>
#import <YYImage/YYImageCoder.h>
#import <YYImage/YYAnimatedImageView.h>
#elif __has_include(<YYWebImage/YYImage.h>)
#import <YYWebImage/YYFrameImage.h>
#import <YYWebImage/YYSpriteSheetImage.h>
#import <YYWebImage/YYImageCoder.h>
#import <YYWebImage/YYAnimatedImageView.h>
#else
#import "YYFrameImage.h"
#import "YYSpriteSheetImage.h"
#import "YYImageCoder.h"
#import "YYAnimatedImageView.h"
#endif
NS_ASSUME_NONNULL_BEGIN
/**
A YYImage object is a high-level way to display animated image data.
@discussion It is a fully compatible `UIImage` subclass. It extends the UIImage
to support animated WebP, APNG and GIF format image data decoding. It also
support NSCoding protocol to archive and unarchive multi-frame image data.
If the image is created from multi-frame image data, and you want to play the
animation, try replace UIImageView with `YYAnimatedImageView`.
Sample Code:
// animation@3x.webp
YYImage *image = [YYImage imageNamed:@"animation.webp"];
YYAnimatedImageView *imageView = [YYAnimatedImageView alloc] initWithImage:image];
[view addSubView:imageView];
*/
@interface YYImage : UIImage <YYAnimatedImage>
+ (nullable YYImage *)imageNamed:(NSString *)name; // no cache!
+ (nullable YYImage *)imageWithContentsOfFile:(NSString *)path;
+ (nullable YYImage *)imageWithData:(NSData *)data;
+ (nullable YYImage *)imageWithData:(NSData *)data scale:(CGFloat)scale;
/**
If the image is created from data or file, then the value indicates the data type.
*/
@property (nonatomic, readonly) YYImageType animatedImageType;
/**
If the image is created from animated image data (multi-frame GIF/APNG/WebP),
this property stores the original image data.
*/
@property (nullable, nonatomic, readonly) NSData *animatedImageData;
/**
The total memory usage (in bytes) if all frame images was loaded into memory.
The value is 0 if the image is not created from a multi-frame image data.
*/
@property (nonatomic, readonly) NSUInteger animatedImageMemorySize;
/**
Preload all frame image to memory.
@discussion Set this property to `YES` will block the calling thread to decode
all animation frame image to memory, set to `NO` will release the preloaded frames.
If the image is shared by lots of image views (such as emoticon), preload all
frames will reduce the CPU cost.
See `animatedImageMemorySize` for memory cost.
*/
@property (nonatomic) BOOL preloadAllAnimatedImageFrames;
@end
NS_ASSUME_NONNULL_END
================================================
FILE: Demo/YYTextDemo/YYImage/YYImage.m
================================================
//
// YYImage.m
// YYImage <https://github.com/ibireme/YYImage>
//
// Created by ibireme on 14/10/20.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "YYImage.h"
/**
An array of NSNumber objects, shows the best order for path scale search.
e.g. iPhone3GS:@[@1,@2,@3] iPhone5:@[@2,@3,@1] iPhone6 Plus:@[@3,@2,@1]
*/
static NSArray *_NSBundlePreferredScales() {
static NSArray *scales;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
CGFloat screenScale = [UIScreen mainScreen].scale;
if (screenScale <= 1) {
scales = @[@1,@2,@3];
} else if (screenScale <= 2) {
scales = @[@2,@3,@1];
} else {
scales = @[@3,@2,@1];
}
});
return scales;
}
/**
Add scale modifier to the file name (without path extension),
From @"name" to @"name@2x".
e.g.
<table>
<tr><th>Before </th><th>After(scale:2)</th></tr>
<tr><td>"icon" </td><td>"icon@2x" </td></tr>
<tr><td>"icon " </td><td>"icon @2x" </td></tr>
<tr><td>"icon.top" </td><td>"icon.top@2x" </td></tr>
<tr><td>"/p/name" </td><td>"/p/name@2x" </td></tr>
<tr><td>"/path/" </td><td>"/path/" </td></tr>
</table>
@param scale Resource scale.
@return String by add scale modifier, or just return if it's not end with file name.
*/
static NSString *_NSStringByAppendingNameScale(NSString *string, CGFloat scale) {
if (!string) return nil;
if (fabs(scale - 1) <= __FLT_EPSILON__ || string.length == 0 || [string hasSuffix:@"/"]) return string.copy;
return [string stringByAppendingFormat:@"@%@x", @(scale)];
}
/**
Return the path scale.
e.g.
<table>
<tr><th>Path </th><th>Scale </th></tr>
<tr><td>"icon.png" </td><td>1 </td></tr>
<tr><td>"icon@2x.png" </td><td>2 </td></tr>
<tr><td>"icon@2.5x.png" </td><td>2.5 </td></tr>
<tr><td>"icon@2x" </td><td>1 </td></tr>
<tr><td>"icon@2x..png" </td><td>1 </td></tr>
<tr><td>"icon@2x.png/" </td><td>1 </td></tr>
</table>
*/
static CGFloat _NSStringPathScale(NSString *string) {
if (string.length == 0 || [string hasSuffix:@"/"]) return 1;
NSString *name = string.stringByDeletingPathExtension;
__block CGFloat scale = 1;
NSRegularExpression *pattern = [NSRegularExpression regularExpressionWithPattern:@"@[0-9]+\\.?[0-9]*x$" options:NSRegularExpressionAnchorsMatchLines error:nil];
[pattern enumerateMatchesInString:name options:kNilOptions range:NSMakeRange(0, name.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
if (result.range.location >= 3) {
scale = [string substringWithRange:NSMakeRange(result.range.location + 1, result.range.length - 2)].doubleValue;
}
}];
return scale;
}
@implementation YYImage {
YYImageDecoder *_decoder;
NSArray *_preloadedFrames;
dispatch_semaphore_t _preloadedLock;
NSUInteger _bytesPerFrame;
}
+ (YYImage *)imageNamed:(NSString *)name {
if (name.length == 0) return nil;
if ([name hasSuffix:@"/"]) return nil;
NSString *res = name.stringByDeletingPathExtension;
NSString *ext = name.pathExtension;
NSString *path = nil;
CGFloat scale = 1;
// If no extension, guess by system supported (same as UIImage).
NSArray *exts = ext.length > 0 ? @[ext] : @[@"", @"png", @"jpeg", @"jpg", @"gif", @"webp", @"apng"];
NSArray *scales = _NSBundlePreferredScales();
for (int s = 0; s < scales.count; s++) {
scale = ((NSNumber *)scales[s]).floatValue;
NSString *scaledName = _NSStringByAppendingNameScale(res, scale);
for (NSString *e in exts) {
path = [[NSBundle mainBundle] pathForResource:scaledName ofType:e];
if (path) break;
}
if (path) break;
}
if (path.length == 0) return nil;
NSData *data = [NSData dataWithContentsOfFile:path];
if (data.length == 0) return nil;
return [[self alloc] initWithData:data scale:scale];
}
+ (YYImage *)imageWithContentsOfFile:(NSString *)path {
return [[self alloc] initWithContentsOfFile:path];
}
+ (YYImage *)imageWithData:(NSData *)data {
return [[self alloc] initWithData:data];
}
+ (YYImage *)imageWithData:(NSData *)data scale:(CGFloat)scale {
return [[self alloc] initWithData:data scale:scale];
}
- (instancetype)initWithContentsOfFile:(NSString *)path {
NSData *data = [NSData dataWithContentsOfFile:path];
return [self initWithData:data scale:_NSStringPathScale(path)];
}
- (instancetype)initWithData:(NSData *)data {
return [self initWithData:data scale:1];
}
- (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale {
if (data.length == 0) return nil;
if (scale <= 0) scale = [UIScreen mainScreen].scale;
_preloadedLock = dispatch_semaphore_create(1);
@autoreleasepool {
YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:scale];
YYImageFrame *frame = [decoder frameAtIndex:0 decodeForDisplay:YES];
UIImage *image = frame.image;
if (!image) return nil;
self = [self initWithCGImage:image.CGImage scale:decoder.scale orientation:image.imageOrientation];
if (!self) return nil;
_animatedImageType = decoder.type;
if (decoder.frameCount > 1) {
_decoder = decoder;
_bytesPerFrame = CGImageGetBytesPerRow(image.CGImage) * CGImageGetHeight(image.CGImage);
_animatedImageMemorySize = _bytesPerFrame * decoder.frameCount;
}
self.yy_isDecodedForDisplay = YES;
}
return self;
}
- (NSData *)animatedImageData {
return _decoder.data;
}
- (void)setPreloadAllAnimatedImageFrames:(BOOL)preloadAllAnimatedImageFrames {
if (_preloadAllAnimatedImageFrames != preloadAllAnimatedImageFrames) {
if (preloadAllAnimatedImageFrames && _decoder.frameCount > 0) {
NSMutableArray *frames = [NSMutableArray new];
for (NSUInteger i = 0, max = _decoder.frameCount; i < max; i++) {
UIImage *img = [self animatedImageFrameAtIndex:i];
if (img) {
[frames addObject:img];
} else {
[frames addObject:[NSNull null]];
}
}
dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER);
_preloadedFrames = frames;
dispatch_semaphore_signal(_preloadedLock);
} else {
dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER);
_preloadedFrames = nil;
dispatch_semaphore_signal(_preloadedLock);
}
}
}
#pragma mark - protocol NSCoding
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
NSNumber *scale = [aDecoder decodeObjectForKey:@"YYImageScale"];
NSData *data = [aDecoder decodeObjectForKey:@"YYImageData"];
if (data.length) {
self = [self initWithData:data scale:scale.doubleValue];
} else {
self = [super initWithCoder:aDecoder];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
if (_decoder.data.length) {
[aCoder encodeObject:@(self.scale) forKey:@"YYImageScale"];
[aCoder encodeObject:_decoder.data forKey:@"YYImageData"];
} else {
[super encodeWithCoder:aCoder]; // Apple use UIImagePNGRepresentation() to encode UIImage.
}
}
#pragma mark - protocol YYAnimatedImage
- (NSUInteger)animatedImageFrameCount {
return _decoder.frameCount;
}
- (NSUInteger)animatedImageLoopCount {
return _decoder.loopCount;
}
- (NSUInteger)animatedImageBytesPerFrame {
return _bytesPerFrame;
}
- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
if (index >= _decoder.frameCount) return nil;
dispatch_semaphore_wait(_preloadedLock, DISPATCH_TIME_FOREVER);
UIImage *image = _preloadedFrames[index];
dispatch_semaphore_signal(_preloadedLock);
if (image) return image == (id)[NSNull null] ? nil : image;
return [_decoder frameAtIndex:index decodeForDisplay:YES].image;
}
- (NSTimeInterval)animatedImageDurationAtIndex:(NSUInteger)index {
NSTimeInterval duration = [_decoder frameDurationAtIndex:index];
/*
http://opensource.apple.com/source/WebCore/WebCore-7600.1.25/platform/graphics/cg/ImageSourceCG.cpp
Many annoying ads specify a 0 duration to make an image flash as quickly as
possible. We follow Safari and Firefox's behavior and use a duration of 100 ms
for any frames that specify a duration of <= 10 ms.
See <rdar://problem/7689300> and <http://webkit.org/b/36082> for more information.
See also: http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser.
*/
if (duration < 0.011f) return 0.100f;
return duration;
}
@end
================================================
FILE: Demo/YYTextDemo/YYImage/YYImageCoder.h
================================================
//
// YYImageCoder.h
// YYImage <https://github.com/ibireme/YYImage>
//
// Created by ibireme on 15/5/13.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
Image file type.
*/
typedef NS_ENUM(NSUInteger, YYImageType) {
YYImageTypeUnknown = 0, ///< unknown
YYImageTypeJPEG, ///< jpeg, jpg
YYImageTypeJPEG2000, ///< jp2
YYImageTypeTIFF, ///< tiff, tif
YYImageTypeBMP, ///< bmp
YYImageTypeICO, ///< ico
YYImageTypeICNS, ///< icns
YYImageTypeGIF, ///< gif
YYImageTypePNG, ///< png
YYImageTypeWebP, ///< webp
YYImageTypeOther, ///< other image format
};
/**
Dispose method specifies how the area used by the current frame is to be treated
before rendering the next frame on the canvas.
*/
typedef NS_ENUM(NSUInteger, YYImageDisposeMethod) {
/**
No disposal is done on this frame before rendering the next; the contents
of the canvas are left as is.
*/
YYImageDisposeNone = 0,
/**
The frame's region of the canvas is to be cleared to fully transparent black
before rendering the next frame.
*/
YYImageDisposeBackground,
/**
The frame's region of the canvas is to be reverted to the previous contents
before rendering the next frame.
*/
YYImageDisposePrevious,
};
/**
Blend operation specifies how transparent pixels of the current frame are
blended with those of the previous canvas.
*/
typedef NS_ENUM(NSUInteger, YYImageBlendOperation) {
/**
All color components of the frame, including alpha, overwrite the current
contents of the frame's canvas region.
*/
YYImageBlendNone = 0,
/**
The frame should be composited onto the output buffer based on its alpha.
*/
YYImageBlendOver,
};
/**
An image frame object.
*/
@interface YYImageFrame : NSObject <NSCopying>
@property (nonatomic) NSUInteger index; ///< Frame index (zero based)
@property (nonatomic) NSUInteger width; ///< Frame width
@property (nonatomic) NSUInteger height; ///< Frame height
@property (nonatomic) NSUInteger offsetX; ///< Frame origin.x in canvas (left-bottom based)
@property (nonatomic) NSUInteger offsetY; ///< Frame origin.y in canvas (left-bottom based)
@property (nonatomic) NSTimeInterval duration; ///< Frame duration in seconds
@property (nonatomic) YYImageDisposeMethod dispose; ///< Frame dispose method.
@property (nonatomic) YYImageBlendOperation blend; ///< Frame blend operation.
@property (nullable, nonatomic, strong) UIImage *image; ///< The image.
+ (instancetype)frameWithImage:(UIImage *)image;
@end
#pragma mark - Decoder
/**
An image decoder to decode image data.
@discussion This class supports decoding animated WebP, APNG, GIF and system
image format such as PNG, JPG, JP2, BMP, TIFF, PIC, ICNS and ICO. It can be used
to decode complete image data, or to decode incremental image data during image
download. This class is thread-safe.
Example:
// Decode single image:
NSData *data = [NSData dataWithContentOfFile:@"/tmp/image.webp"];
YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:2.0];
UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image;
// Decode image during download:
NSMutableData *data = [NSMutableData new];
YYImageDecoder *decoder = [[YYImageDecoder alloc] initWithScale:2.0];
while(newDataArrived) {
[data appendData:newData];
[decoder updateData:data final:NO];
if (decoder.frameCount > 0) {
UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image;
// progressive display...
}
}
[decoder updateData:data final:YES];
UIImage image = [decoder frameAtIndex:0 decodeForDisplay:YES].image;
// final display...
*/
@interface YYImageDecoder : NSObject
@property (nullable, nonatomic, readonly) NSData *data; ///< Image data.
@property (nonatomic, readonly) YYImageType type; ///< Image data type.
@property (nonatomic, readonly) CGFloat scale; ///< Image scale.
@property (nonatomic, readonly) NSUInteger frameCount; ///< Image frame count.
@property (nonatomic, readonly) NSUInteger loopCount; ///< Image loop count, 0 means infinite.
@property (nonatomic, readonly) NSUInteger width; ///< Image canvas width.
@property (nonatomic, readonly) NSUInteger height; ///< Image canvas height.
@property (nonatomic, readonly, getter=isFinalized) BOOL finalized;
/**
Creates an image decoder.
@param scale Image's scale.
@return An image decoder.
*/
- (instancetype)initWithScale:(CGFloat)scale NS_DESIGNATED_INITIALIZER;
/**
Updates the incremental image with new data.
@discussion You can use this method to decode progressive/interlaced/baseline
image when you do not have the complete image data. The `data` was retained by
decoder, you should not modify the data in other thread during decoding.
@param data The data to add to the image decoder. Each time you call this
function, the 'data' parameter must contain all of the image file data
accumulated so far.
@param final A value that specifies whether the data is the final set.
Pass YES if it is, NO otherwise. When the data is already finalized, you can
not update the data anymore.
@return Whether succeed.
*/
- (BOOL)updateData:(nullable NSData *)data final:(BOOL)final;
/**
Convenience method to create a decoder with specified data.
@param data Image data.
@param scale Image's scale.
@return A new decoder, or nil if an error occurs.
*/
+ (nullable instancetype)decoderWithData:(NSData *)data scale:(CGFloat)scale;
/**
Decodes and returns a frame from a specified index.
@param index Frame image index (zero-based).
@param decodeForDisplay Whether decode the image to memory bitmap for display.
If NO, it will try to returns the original frame data without blend.
@return A new frame with image, or nil if an error occurs.
*/
- (nullable YYImageFrame *)frameAtIndex:(NSUInteger)index decodeForDisplay:(BOOL)decodeForDisplay;
/**
Returns the frame duration from a specified index.
@param index Frame image (zero-based).
@return Duration in seconds.
*/
- (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index;
/**
Returns the frame's properties. See "CGImageProperties.h" in ImageIO.framework
for more information.
@param index Frame image index (zero-based).
@return The ImageIO frame property.
*/
- (nullable NSDictionary *)framePropertiesAtIndex:(NSUInteger)index;
/**
Returns the image's properties. See "CGImageProperties.h" in ImageIO.framework
for more information.
*/
- (nullable NSDictionary *)imageProperties;
@end
#pragma mark - Encoder
/**
An image encoder to encode image to data.
@discussion It supports encoding single frame image with the type defined in YYImageType.
It also supports encoding multi-frame image with GIF, APNG and WebP.
Example:
YYImageEncoder *jpegEncoder = [[YYImageEncoder alloc] initWithType:YYImageTypeJPEG];
jpegEncoder.quality = 0.9;
[jpegEncoder addImage:image duration:0];
NSData jpegData = [jpegEncoder encode];
YYImageEncoder *gifEncoder = [[YYImageEncoder alloc] initWithType:YYImageTypeGIF];
gifEncoder.loopCount = 5;
[gifEncoder addImage:image0 duration:0.1];
[gifEncoder addImage:image1 duration:0.15];
[gifEncoder addImage:image2 duration:0.2];
NSData gifData = [gifEncoder encode];
@warning It just pack the images together when encoding multi-frame image. If you
want to reduce the image file size, try imagemagick/ffmpeg for GIF and WebP,
and apngasm for APNG.
*/
@interface YYImageEncoder : NSObject
@property (nonatomic, readonly) YYImageType type; ///< Image type.
@property (nonatomic) NSUInteger loopCount; ///< Loop count, 0 means infinit, only available for GIF/APNG/WebP.
@property (nonatomic) BOOL lossless; ///< Lossless, only available for WebP.
@property (nonatomic) CGFloat quality; ///< Compress quality, 0.0~1.0, only available for JPG/JP2/WebP.
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;
/**
Create an image encoder with a specified type.
@param type Image type.
@return A new encoder, or nil if an error occurs.
*/
- (nullable instancetype)initWithType:(YYImageType)type NS_DESIGNATED_INITIALIZER;
/**
Add an image to encoder.
@param image Image.
@param duration Image duration for animation. Pass 0 to ignore this parameter.
*/
- (void)addImage:(UIImage *)image duration:(NSTimeInterval)duration;
/**
Add an image with image data to encoder.
@param data Image data.
@param duration Image duration for animation. Pass 0 to ignore this parameter.
*/
- (void)addImageWithData:(NSData *)data duration:(NSTimeInterval)duration;
/**
Add an image from a file path to encoder.
@param image Image file path.
@param duration Image duration for animation. Pass 0 to ignore this parameter.
*/
- (void)addImageWithFile:(NSString *)path duration:(NSTimeInterval)duration;
/**
Encodes the image and returns the image data.
@return The image data, or nil if an error occurs.
*/
- (nullable NSData *)encode;
/**
Encodes the image to a file.
@param path The file path (overwrite if exist).
@return Whether succeed.
*/
- (BOOL)encodeToFile:(NSString *)path;
/**
Convenience method to encode single frame image.
@param image The image.
@param type The destination image type.
@param quality Image quality, 0.0~1.0.
@return The image data, or nil if an error occurs.
*/
+ (nullable NSData *)encodeImage:(UIImage *)image type:(YYImageType)type quality:(CGFloat)quality;
/**
Convenience method to encode image from a decoder.
@param decoder The image decoder.
@param type The destination image type;
@param quality Image quality, 0.0~1.0.
@return The image data, or nil if an error occurs.
*/
+ (nullable NSData *)encodeImageWithDecoder:(YYImageDecoder *)decoder type:(YYImageType)type quality:(CGFloat)quality;
@end
#pragma mark - UIImage
@interface UIImage (YYImageCoder)
/**
Decompress this image to bitmap, so when the image is displayed on screen,
the main thread won't be blocked by additional decode. If the image has already
been decoded or unable to decode, it just returns itself.
@return an image decoded, or just return itself if no needed.
@see yy_isDecodedForDisplay
*/
- (instancetype)yy_imageByDecoded;
/**
Wherher the image can be display on screen without additional decoding.
@warning It just a hint for your code, change it has no other effect.
*/
@property (nonatomic) BOOL yy_isDecodedForDisplay;
/**
Saves this image to iOS Photos Album.
@discussion This method attempts to save the original data to album if the
image is created from an animated GIF/APNG, otherwise, it will save the image
as JPEG or PNG (based on the alpha information).
@param completionBlock The block invoked (in main thread) after the save operation completes.
assetURL: An URL that identifies the saved image file. If the image is not saved, assetURL is nil.
error: If the image is not saved, an error object that describes the reason for failure, otherwise nil.
*/
- (void)yy_saveToAlbumWithCompletionBlock:(nullable void(^)(NSURL * _Nullable assetURL, NSError * _Nullable error))completionBlock;
/**
Return a 'best' data representation for this image.
@discussion The convertion based on these rule:
1. If the image is created from an animated GIF/APNG/WebP, it returns the original data.
2. It returns PNG or JPEG(0.9) representation based on the alpha information.
@return Image data, or nil if an error occurs.
*/
- (nullable NSData *)yy_imageDataRepresentation;
@end
#pragma mark - Helper
/// Detect a data's image type by reading the data's header 16 bytes (very fast).
CG_EXTERN YYImageType YYImageDetectType(CFDataRef data);
/// Convert YYImageType to UTI (such as kUTTypeJPEG).
CG_EXTERN CFStringRef _Nullable YYImageTypeToUTType(YYImageType type);
/// Convert UTI (such as kUTTypeJPEG) to YYImageType.
CG_EXTERN YYImageType YYImageTypeFromUTType(CFStringRef uti);
/// Get image type's file extension (such as @"jpg").
CG_EXTERN NSString *_Nullable YYImageTypeGetExtension(YYImageType type);
/// Returns the shared DeviceRGB color space.
CG_EXTERN CGColorSpaceRef YYCGColorSpaceGetDeviceRGB();
/// Returns the shared DeviceGray color space.
CG_EXTERN CGColorSpaceRef YYCGColorSpaceGetDeviceGray();
/// Returns whether a color space is DeviceRGB.
CG_EXTERN BOOL YYCGColorSpaceIsDeviceRGB(CGColorSpaceRef space);
/// Returns whether a color space is DeviceGray.
CG_EXTERN BOOL YYCGColorSpaceIsDeviceGray(CGColorSpaceRef space);
/// Convert EXIF orientation value to UIImageOrientation.
CG_EXTERN UIImageOrientation YYUIImageOrientationFromEXIFValue(NSInteger value);
/// Convert UIImageOrientation to EXIF orientation value.
CG_EXTERN NSInteger YYUIImageOrientationToEXIFValue(UIImageOrientation orientation);
/**
Create a decoded image.
@discussion If the source image is created from a compressed image data (such as
PNG or JPEG), you can use this method to decode the image. After decoded, you can
access the decoded bytes with CGImageGetDataProvider() and CGDataProviderCopyData()
without additional decode process. If the image has already decoded, this method
just copy the decoded bytes to the new image.
@param imageRef The source image.
@param decodeForDisplay If YES, this method will decode the image and convert
it to BGRA8888 (premultiplied) or BGRX8888 format for CALayer display.
@return A decoded image, or NULL if an error occurs.
*/
CG_EXTERN CGImageRef _Nullable YYCGImageCreateDecodedCopy(CGImageRef imageRef, BOOL decodeForDisplay);
/**
Create an image copy with an orientation.
@param imageRef Source image
@param orientation Image orientation which will applied to the image.
@param destBitmapInfo Destimation image bitmap, only support 32bit format (such as ARGB8888).
@return A new image, or NULL if an error occurs.
*/
CG_EXTERN CGImageRef _Nullable YYCGImageCreateCopyWithOrientation(CGImageRef imageRef,
UIImageOrientation orientation,
CGBitmapInfo destBitmapInfo);
/**
Create an image copy with CGAffineTransform.
@param imageRef Source image.
@param transform Transform applied to image (left-bottom based coordinate system).
@param destSize Destination image size
@param destBitmapInfo Destimation image bitmap, only support 32bit format (such as ARGB8888).
@return A new image, or NULL if an error occurs.
*/
CG_EXTERN CGImageRef _Nullable YYCGImageCreateAffineTransformCopy(CGImageRef imageRef,
CGAffineTransform transform,
CGSize destSize,
CGBitmapInfo destBitmapInfo);
/**
Encode an image to data with CGImageDestination.
@param imageRef The image.
@param type The image destination data type.
@param quality The quality (0.0~1.0)
@return A new image data, or nil if an error occurs.
*/
CG_EXTERN CFDataRef _Nullable YYCGImageCreateEncodedData(CGImageRef imageRef, YYImageType type, CGFloat quality);
/**
Whether WebP is available in YYImage.
*/
CG_EXTERN BOOL YYImageWebPAvailable();
/**
Get a webp image frame count;
@param webpData WebP data.
@return Image frame count, or 0 if an error occurs.
*/
CG_EXTERN NSUInteger YYImageGetWebPFrameCount(CFDataRef webpData);
/**
Decode an image from WebP data, returns NULL if an error occurs.
@param webpData The WebP data.
@param decodeForDisplay If YES, this method will decode the image and convert it
to BGRA8888 (premultiplied) format for CALayer display.
@param useThreads YES to enable multi-thread decode.
(speed up, but cost more CPU)
@param bypassFiltering YES to skip the in-loop filtering.
(speed up, but may lose some smooth)
@param noFancyUpsampling YES to use faster pointwise upsampler.
(speed down, and may lose some details).
@return The decoded image, or NULL if an error occurs.
*/
CG_EXTERN CGImageRef _Nullable YYCGImageCreateWithWebPData(CFDataRef webpData,
BOOL decodeForDisplay,
BOOL useThreads,
BOOL bypassFiltering,
BOOL noFancyUpsampling);
typedef NS_ENUM(NSUInteger, YYImagePreset) {
YYImagePresetDefault = 0, ///< default preset.
YYImagePresetPicture, ///< digital picture, like portrait, inner shot
YYImagePresetPhoto, ///< outdoor photograph, with natural lighting
YYImagePresetDrawing, ///< hand or line drawing, with high-contrast details
YYImagePresetIcon, ///< small-sized colorful images
YYImagePresetText ///< text-like
};
/**
Encode a CGImage to WebP data
gitextract_s7a5jp6t/ ├── .gitignore ├── .travis.yml ├── Demo/ │ ├── YYTextDemo/ │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets/ │ │ │ └── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ ├── Base.lproj/ │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── CALayer+YYAdd.h │ │ ├── CALayer+YYAdd.m │ │ ├── EmoticonQQ.bundle/ │ │ │ └── info.plist │ │ ├── Info.plist │ │ ├── NSBundle+YYAdd.h │ │ ├── NSBundle+YYAdd.m │ │ ├── NSData+YYAdd.h │ │ ├── NSData+YYAdd.m │ │ ├── NSString+YYAdd.h │ │ ├── NSString+YYAdd.m │ │ ├── UIControl+YYAdd.h │ │ ├── UIControl+YYAdd.m │ │ ├── UIGestureRecognizer+YYAdd.h │ │ ├── UIGestureRecognizer+YYAdd.m │ │ ├── UIImage+YYWebImage.h │ │ ├── UIImage+YYWebImage.m │ │ ├── UIView+YYAdd.h │ │ ├── UIView+YYAdd.m │ │ ├── ViewController.h │ │ ├── ViewController.m │ │ ├── YYFPSLabel.h │ │ ├── YYFPSLabel.m │ │ ├── YYGestureRecognizer.h │ │ ├── YYGestureRecognizer.m │ │ ├── YYImage/ │ │ │ ├── Animated image support.txt │ │ │ ├── YYAnimatedImageView.h │ │ │ ├── YYAnimatedImageView.m │ │ │ ├── YYFrameImage.h │ │ │ ├── YYFrameImage.m │ │ │ ├── YYImage.h │ │ │ ├── YYImage.m │ │ │ ├── YYImageCoder.h │ │ │ ├── YYImageCoder.m │ │ │ ├── YYSpriteSheetImage.h │ │ │ └── YYSpriteSheetImage.m │ │ ├── YYTextAsyncExample.h │ │ ├── YYTextAsyncExample.m │ │ ├── YYTextAttachmentExample.h │ │ ├── YYTextAttachmentExample.m │ │ ├── YYTextAttributeExample.h │ │ ├── YYTextAttributeExample.m │ │ ├── YYTextBindingExample.h │ │ ├── YYTextBindingExample.m │ │ ├── YYTextCopyPasteExample.h │ │ ├── YYTextCopyPasteExample.m │ │ ├── YYTextEditExample.h │ │ ├── YYTextEditExample.m │ │ ├── YYTextEmoticonExample.h │ │ ├── YYTextEmoticonExample.m │ │ ├── YYTextExample.h │ │ ├── YYTextExample.m │ │ ├── YYTextExampleHelper.h │ │ ├── YYTextExampleHelper.m │ │ ├── YYTextMarkdownExample.h │ │ ├── YYTextMarkdownExample.m │ │ ├── YYTextRubyExample.h │ │ ├── YYTextRubyExample.m │ │ ├── YYTextTagExample.h │ │ ├── YYTextTagExample.m │ │ ├── YYTextUndoRedoExample.h │ │ ├── YYTextUndoRedoExample.m │ │ ├── YYWeakProxy.h │ │ ├── YYWeakProxy.m │ │ └── main.m │ └── YYTextDemo.xcodeproj/ │ ├── project.pbxproj │ └── project.xcworkspace/ │ └── contents.xcworkspacedata ├── Framework/ │ ├── Info.plist │ └── YYText.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ └── contents.xcworkspacedata │ └── xcshareddata/ │ └── xcschemes/ │ └── YYText.xcscheme ├── LICENSE ├── README.md ├── YYText/ │ ├── Component/ │ │ ├── YYTextContainerView.h │ │ ├── YYTextContainerView.m │ │ ├── YYTextDebugOption.h │ │ ├── YYTextDebugOption.m │ │ ├── YYTextEffectWindow.h │ │ ├── YYTextEffectWindow.m │ │ ├── YYTextInput.h │ │ ├── YYTextInput.m │ │ ├── YYTextKeyboardManager.h │ │ ├── YYTextKeyboardManager.m │ │ ├── YYTextLayout.h │ │ ├── YYTextLayout.m │ │ ├── YYTextLine.h │ │ ├── YYTextLine.m │ │ ├── YYTextMagnifier.h │ │ ├── YYTextMagnifier.m │ │ ├── YYTextSelectionView.h │ │ └── YYTextSelectionView.m │ ├── String/ │ │ ├── YYTextArchiver.h │ │ ├── YYTextArchiver.m │ │ ├── YYTextAttribute.h │ │ ├── YYTextAttribute.m │ │ ├── YYTextParser.h │ │ ├── YYTextParser.m │ │ ├── YYTextRubyAnnotation.h │ │ ├── YYTextRubyAnnotation.m │ │ ├── YYTextRunDelegate.h │ │ └── YYTextRunDelegate.m │ ├── Utility/ │ │ ├── NSAttributedString+YYText.h │ │ ├── NSAttributedString+YYText.m │ │ ├── NSParagraphStyle+YYText.h │ │ ├── NSParagraphStyle+YYText.m │ │ ├── UIPasteboard+YYText.h │ │ ├── UIPasteboard+YYText.m │ │ ├── UIView+YYText.h │ │ ├── UIView+YYText.m │ │ ├── YYTextAsyncLayer.h │ │ ├── YYTextAsyncLayer.m │ │ ├── YYTextTransaction.h │ │ ├── YYTextTransaction.m │ │ ├── YYTextUtilities.h │ │ ├── YYTextUtilities.m │ │ ├── YYTextWeakProxy.h │ │ └── YYTextWeakProxy.m │ ├── YYLabel.h │ ├── YYLabel.m │ ├── YYText.h │ ├── YYTextView.h │ └── YYTextView.m └── YYText.podspec
SYMBOL INDEX (52 symbols across 3 files)
FILE: Demo/YYTextDemo/YYGestureRecognizer.h
type YYGestureRecognizerStateBegan (line 15) | typedef NS_ENUM(NSUInteger, YYGestureRecognizerState) {
FILE: YYText/Component/YYTextKeyboardManager.h
function NS_ASSUME_NONNULL_BEGIN (line 14) | NS_ASSUME_NONNULL_BEGIN
FILE: YYText/Utility/YYTextUtilities.h
function NS_ASSUME_NONNULL_BEGIN (line 25) | NS_ASSUME_NONNULL_BEGIN
function BOOL (line 60) | static inline BOOL YYTextIsLinebreakString(NSString * _Nullable str) {
function NSUInteger (line 76) | static inline NSUInteger YYTextLinebreakTailLength(NSString * _Nullable ...
function NSTextCheckingType (line 99) | static inline NSTextCheckingType YYTextNSTextCheckingTypeFromUIDataDetec...
function BOOL (line 114) | static inline BOOL YYTextUIFontIsEmoji(UIFont *font) {
function BOOL (line 124) | static inline BOOL YYTextCTFontIsEmoji(CTFontRef font) {
function BOOL (line 138) | static inline BOOL YYTextCGFontIsEmoji(CGFontRef font) {
function BOOL (line 153) | static inline BOOL YYTextCTFontContainsColorBitmapGlyphs(CTFontRef font) {
function BOOL (line 164) | static inline BOOL YYTextCGGlyphIsBitmap(CTFontRef font, CGGlyph glyph) {
function CGFloat (line 182) | static inline CGFloat YYTextEmojiGetAscentWithFontSize(CGFloat fontSize) {
function CGFloat (line 199) | static inline CGFloat YYTextEmojiGetDescentWithFontSize(CGFloat fontSize) {
function CGRect (line 217) | static inline CGRect YYTextEmojiGetGlyphBoundingRectWithFontSize(CGFloat...
function CGFloat (line 247) | static inline CGFloat YYTextDegreesToRadians(CGFloat degrees) {
function CGFloat (line 252) | static inline CGFloat YYTextRadiansToDegrees(CGFloat radians) {
function CGFloat (line 260) | static inline CGFloat YYTextCGAffineTransformGetRotation(CGAffineTransfo...
function CGFloat (line 265) | static inline CGFloat YYTextCGAffineTransformGetScaleX(CGAffineTransform...
function CGFloat (line 270) | static inline CGFloat YYTextCGAffineTransformGetScaleY(CGAffineTransform...
function CGFloat (line 275) | static inline CGFloat YYTextCGAffineTransformGetTranslateX(CGAffineTrans...
function CGFloat (line 280) | static inline CGFloat YYTextCGAffineTransformGetTranslateY(CGAffineTrans...
function CGAffineTransform (line 299) | static inline CGAffineTransform YYTextCGAffineTransformMakeSkew(CGFloat ...
function UIEdgeInsets (line 307) | static inline UIEdgeInsets YYTextUIEdgeInsetsInvert(UIEdgeInsets insets) {
function CGPoint (line 331) | static inline CGPoint YYTextCGRectGetCenter(CGRect rect) {
function CGFloat (line 336) | static inline CGFloat YYTextCGRectGetArea(CGRect rect) {
function CGFloat (line 343) | static inline CGFloat YYTextCGPointGetDistanceToPoint(CGPoint p1, CGPoin...
function CGFloat (line 348) | static inline CGFloat YYTextCGPointGetDistanceToRect(CGPoint p, CGRect r) {
function CGFloat (line 373) | static inline CGFloat YYTextCGFloatToPixel(CGFloat value) {
function CGFloat (line 378) | static inline CGFloat YYTextCGFloatFromPixel(CGFloat value) {
function CGFloat (line 383) | static inline CGFloat YYTextCGFloatPixelFloor(CGFloat value) {
function CGFloat (line 389) | static inline CGFloat YYTextCGFloatPixelRound(CGFloat value) {
function CGFloat (line 395) | static inline CGFloat YYTextCGFloatPixelCeil(CGFloat value) {
function CGFloat (line 401) | static inline CGFloat YYTextCGFloatPixelHalf(CGFloat value) {
function CGPoint (line 407) | static inline CGPoint YYTextCGPointPixelFloor(CGPoint point) {
function CGPoint (line 414) | static inline CGPoint YYTextCGPointPixelRound(CGPoint point) {
function CGPoint (line 421) | static inline CGPoint YYTextCGPointPixelCeil(CGPoint point) {
function CGPoint (line 428) | static inline CGPoint YYTextCGPointPixelHalf(CGPoint point) {
function CGSize (line 437) | static inline CGSize YYTextCGSizePixelFloor(CGSize size) {
function CGSize (line 444) | static inline CGSize YYTextCGSizePixelRound(CGSize size) {
function CGSize (line 451) | static inline CGSize YYTextCGSizePixelCeil(CGSize size) {
function CGSize (line 458) | static inline CGSize YYTextCGSizePixelHalf(CGSize size) {
function CGRect (line 467) | static inline CGRect YYTextCGRectPixelFloor(CGRect rect) {
function CGRect (line 478) | static inline CGRect YYTextCGRectPixelRound(CGRect rect) {
function CGRect (line 486) | static inline CGRect YYTextCGRectPixelCeil(CGRect rect) {
function CGRect (line 494) | static inline CGRect YYTextCGRectPixelHalf(CGRect rect) {
function UIEdgeInsets (line 504) | static inline UIEdgeInsets YYTextUIEdgeInsetPixelFloor(UIEdgeInsets inse...
function UIEdgeInsets (line 513) | static inline UIEdgeInsets YYTextUIEdgeInsetPixelCeil(UIEdgeInsets inset...
function UIFont (line 523) | static inline UIFont * _Nullable YYTextFontWithBold(UIFont *font) {
function UIFont (line 528) | static inline UIFont * _Nullable YYTextFontWithItalic(UIFont *font) {
function UIFont (line 533) | static inline UIFont * _Nullable YYTextFontWithBoldItalic(UIFont *font) {
function NSRange (line 544) | static inline NSRange YYTextNSRangeFromCFRange(CFRange range) {
function CFRange (line 552) | static inline CFRange YYTextCFRangeFromNSRange(NSRange range) {
Condensed preview — 129 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,339K chars).
[
{
"path": ".gitignore",
"chars": 1430,
"preview": "# OS X\n.DS_Store\n\n# Xcode\n#\n# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore &"
},
{
"path": ".travis.yml",
"chars": 320,
"preview": "language: objective-c\nosx_image: xcode8\nxcode_project: Framework/YYText.xcodeproj\nxcode_scheme: YYText\n\nbefore_install:\n"
},
{
"path": "Demo/YYTextDemo/AppDelegate.h",
"chars": 274,
"preview": "//\n// AppDelegate.h\n// YYTextDemo\n//\n// Created by ibireme on 15/10/17.\n// Copyright © 2015年 ibireme. All rights res"
},
{
"path": "Demo/YYTextDemo/AppDelegate.m",
"chars": 2028,
"preview": "//\n// AppDelegate.m\n// YYTextDemo\n//\n// Created by ibireme on 15/10/17.\n// Copyright © 2015年 ibireme. All rights res"
},
{
"path": "Demo/YYTextDemo/Assets.xcassets/AppIcon.appiconset/Contents.json",
"chars": 1495,
"preview": "{\n \"images\" : [\n {\n \"idiom\" : \"iphone\",\n \"size\" : \"20x20\",\n \"scale\" : \"2x\"\n },\n {\n \"idiom\""
},
{
"path": "Demo/YYTextDemo/Base.lproj/LaunchScreen.storyboard",
"chars": 1740,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
},
{
"path": "Demo/YYTextDemo/Base.lproj/Main.storyboard",
"chars": 1689,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
},
{
"path": "Demo/YYTextDemo/CALayer+YYAdd.h",
"chars": 3312,
"preview": "//\n// CALayer+YYAdd.h\n// YYKit <https://github.com/ibireme/YYKit>\n//\n// Created by ibireme on 14/5/10.\n// Copyright "
},
{
"path": "Demo/YYTextDemo/CALayer+YYAdd.m",
"chars": 7952,
"preview": "//\n// CALayer+YYAdd.m\n// YYKit <https://github.com/ibireme/YYKit>\n//\n// Created by ibireme on 14/5/10.\n// Copyright "
},
{
"path": "Demo/YYTextDemo/EmoticonQQ.bundle/info.plist",
"chars": 8163,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Demo/YYTextDemo/Info.plist",
"chars": 1695,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Demo/YYTextDemo/NSBundle+YYAdd.h",
"chars": 3363,
"preview": "//\n// NSBundle+YYAdd.h\n// YYCategories <https://github.com/ibireme/YYCategories>\n//\n// Created by ibireme on 14/10/20"
},
{
"path": "Demo/YYTextDemo/NSBundle+YYAdd.m",
"chars": 2834,
"preview": "//\n// NSBundle+YYAdd.m\n// YYCategories <https://github.com/ibireme/YYCategories>\n//\n// Created by ibireme on 14/10/20"
},
{
"path": "Demo/YYTextDemo/NSData+YYAdd.h",
"chars": 840,
"preview": "//\n// NSData+YYAdd.h\n// YYKit <https://github.com/ibireme/YYKit>\n//\n// Created by ibireme on 13/4/4.\n// Copyright (c"
},
{
"path": "Demo/YYTextDemo/NSData+YYAdd.m",
"chars": 563,
"preview": "//\n// NSData+YYAdd.m\n// YYKit <https://github.com/ibireme/YYKit>\n//\n// Created by ibireme on 13/4/4.\n// Copyright (c"
},
{
"path": "Demo/YYTextDemo/NSString+YYAdd.h",
"chars": 8838,
"preview": "//\n// NSString+YYAdd.h\n// YYCategories <https://github.com/ibireme/YYCategories>\n//\n// Created by ibireme on 13/4/3.\n"
},
{
"path": "Demo/YYTextDemo/NSString+YYAdd.m",
"chars": 18442,
"preview": "//\n// NSString+YYAdd.m\n// YYCategories <https://github.com/ibireme/YYCategories>\n//\n// Created by ibireme on 13/4/3.\n"
},
{
"path": "Demo/YYTextDemo/UIControl+YYAdd.h",
"chars": 2542,
"preview": "//\n// UIControl+YYAdd.h\n// YYKit <https://github.com/ibireme/YYKit>\n//\n// Created by ibireme on 13/4/5.\n// Copyright"
},
{
"path": "Demo/YYTextDemo/UIControl+YYAdd.m",
"chars": 3457,
"preview": "//\n// UIControl+YYAdd.m\n// YYKit <https://github.com/ibireme/YYKit>\n//\n// Created by ibireme on 13/4/5.\n// Copyright"
},
{
"path": "Demo/YYTextDemo/UIGestureRecognizer+YYAdd.h",
"chars": 1167,
"preview": "//\n// UIGestureRecognizer+YYAdd.h\n// YYKit <https://github.com/ibireme/YYKit>\n//\n// Created by ibireme on 13/10/13.\n/"
},
{
"path": "Demo/YYTextDemo/UIGestureRecognizer+YYAdd.m",
"chars": 1990,
"preview": "//\n// UIGestureRecognizer+YYAdd.m\n// YYKit <https://github.com/ibireme/YYKit>\n//\n// Created by ibireme on 13/10/13.\n/"
},
{
"path": "Demo/YYTextDemo/UIImage+YYWebImage.h",
"chars": 9287,
"preview": "//\n// UIImage+YYWebImage.h\n// YYWebImage <https://github.com/ibireme/YYWebImage>\n//\n// Created by ibireme on 13/4/4.\n"
},
{
"path": "Demo/YYTextDemo/UIImage+YYWebImage.m",
"chars": 28414,
"preview": "//\n// UIImage+YYWebImage.m\n// YYWebImage <https://github.com/ibireme/YYWebImage>\n//\n// Created by ibireme on 13/4/4.\n"
},
{
"path": "Demo/YYTextDemo/UIView+YYAdd.h",
"chars": 3527,
"preview": "//\n// UIView+YYAdd.h\n// YYCategories <https://github.com/ibireme/YYCategories>\n//\n// Created by ibireme on 13/4/3.\n//"
},
{
"path": "Demo/YYTextDemo/UIView+YYAdd.m",
"chars": 6154,
"preview": "//\n// UIView+YYAdd.m\n// YYCategories <https://github.com/ibireme/YYCategories>\n//\n// Created by ibireme on 13/4/3.\n//"
},
{
"path": "Demo/YYTextDemo/ViewController.h",
"chars": 218,
"preview": "//\n// ViewController.h\n// YYTextDemo\n//\n// Created by ibireme on 15/10/17.\n// Copyright © 2015年 ibireme. All rights "
},
{
"path": "Demo/YYTextDemo/ViewController.m",
"chars": 403,
"preview": "//\n// ViewController.m\n// YYTextDemo\n//\n// Created by ibireme on 15/10/17.\n// Copyright © 2015年 ibireme. All rights "
},
{
"path": "Demo/YYTextDemo/YYFPSLabel.h",
"chars": 343,
"preview": "//\n// YYFPSLabel.h\n// YYKitExample\n//\n// Created by ibireme on 15/9/3.\n// Copyright (c) 2015 ibireme. All rights res"
},
{
"path": "Demo/YYTextDemo/YYFPSLabel.m",
"chars": 2322,
"preview": "//\n// YYFPSLabel.m\n// YYKitExample\n//\n// Created by ibireme on 15/9/3.\n// Copyright (c) 2015 ibireme. All rights res"
},
{
"path": "Demo/YYTextDemo/YYGestureRecognizer.h",
"chars": 1188,
"preview": "//\n// YYGestureRecognizer.h\n// YYKit <https://github.com/ibireme/YYKit>\n//\n// Created by ibireme on 14/10/26.\n// Cop"
},
{
"path": "Demo/YYTextDemo/YYGestureRecognizer.m",
"chars": 1794,
"preview": "//\n// YYGestureRecognizer.m\n// YYKit <https://github.com/ibireme/YYKit>\n//\n// Created by ibireme on 14/10/26.\n// Cop"
},
{
"path": "Demo/YYTextDemo/YYImage/Animated image support.txt",
"chars": 102,
"preview": "\nYou may import YYImage (https://github.com/ibireme/YYImage)\nto support animated image copy and paste."
},
{
"path": "Demo/YYTextDemo/YYImage/YYAnimatedImageView.h",
"chars": 4376,
"preview": "//\n// YYAnimatedImageView.h\n// YYImage <https://github.com/ibireme/YYImage>\n//\n// Created by ibireme on 14/10/19.\n// "
},
{
"path": "Demo/YYTextDemo/YYImage/YYAnimatedImageView.m",
"chars": 23375,
"preview": "//\n// YYAnimatedImageView.m\n// YYImage <https://github.com/ibireme/YYImage>\n//\n// Created by ibireme on 14/10/19.\n// "
},
{
"path": "Demo/YYTextDemo/YYImage/YYFrameImage.h",
"chars": 3905,
"preview": "//\n// YYFrameImage.h\n// YYImage <https://github.com/ibireme/YYImage>\n//\n// Created by ibireme on 14/12/9.\n// Copyrig"
},
{
"path": "Demo/YYTextDemo/YYImage/YYFrameImage.m",
"chars": 5398,
"preview": "//\n// YYFrameImage.m\n// YYImage <https://github.com/ibireme/YYImage>\n//\n// Created by ibireme on 14/12/9.\n// Copyrig"
},
{
"path": "Demo/YYTextDemo/YYImage/YYImage.h",
"chars": 2998,
"preview": "//\n// YYImage.h\n// YYImage <https://github.com/ibireme/YYImage>\n//\n// Created by ibireme on 14/10/20.\n// Copyright ("
},
{
"path": "Demo/YYTextDemo/YYImage/YYImage.m",
"chars": 8936,
"preview": "//\n// YYImage.m\n// YYImage <https://github.com/ibireme/YYImage>\n//\n// Created by ibireme on 14/10/20.\n// Copyright ("
},
{
"path": "Demo/YYTextDemo/YYImage/YYImageCoder.h",
"chars": 18507,
"preview": "//\n// YYImageCoder.h\n// YYImage <https://github.com/ibireme/YYImage>\n//\n// Created by ibireme on 15/5/13.\n// Copyrig"
},
{
"path": "Demo/YYTextDemo/YYImage/YYImageCoder.m",
"chars": 113340,
"preview": "//\n// YYImageCoder.m\n// YYImage <https://github.com/ibireme/YYImage>\n//\n// Created by ibireme on 15/5/13.\n// Copyrig"
},
{
"path": "Demo/YYTextDemo/YYImage/YYSpriteSheetImage.h",
"chars": 3549,
"preview": "//\n// YYSpriteImage.h\n// YYImage <https://github.com/ibireme/YYImage>\n//\n// Created by ibireme on 15/4/21.\n// Copyri"
},
{
"path": "Demo/YYTextDemo/YYImage/YYSpriteSheetImage.m",
"chars": 2553,
"preview": "//\n// YYSpriteImage.m\n// YYImage <https://github.com/ibireme/YYImage>\n//\n// Created by ibireme on 15/4/21.\n// Copyri"
},
{
"path": "Demo/YYTextDemo/YYTextAsyncExample.h",
"chars": 219,
"preview": "//\n// YYTextAsyncExample.h\n// YYKitExample\n//\n// Created by ibireme on 15/9/3.\n// Copyright (c) 2015 ibireme. All ri"
},
{
"path": "Demo/YYTextDemo/YYTextAsyncExample.m",
"chars": 6311,
"preview": "//\n// YYTextAsyncExample.m\n// YYKitExample\n//\n// Created by ibireme on 15/9/3.\n// Copyright (c) 2015 ibireme. All ri"
},
{
"path": "Demo/YYTextDemo/YYTextAttachmentExample.h",
"chars": 230,
"preview": "//\n// YYTextAttachmentExample.h\n// YYKitExample\n//\n// Created by ibireme on 15/8/21.\n// Copyright (c) 2015 ibireme. "
},
{
"path": "Demo/YYTextDemo/YYTextAttachmentExample.m",
"chars": 7136,
"preview": "//\n// YYTextAttachmentExample.m\n// YYKitExample\n//\n// Created by ibireme on 15/8/21.\n// Copyright (c) 2015 ibireme. "
},
{
"path": "Demo/YYTextDemo/YYTextAttributeExample.h",
"chars": 228,
"preview": "//\n// YYTextAttributeExample.h\n// YYKitExample\n//\n// Created by ibireme on 15/8/19.\n// Copyright (c) 2015 ibireme. A"
},
{
"path": "Demo/YYTextDemo/YYTextAttributeExample.m",
"chars": 11861,
"preview": "//\n// YYTextAttributeExample.m\n// YYKitExample\n//\n// Created by ibireme on 15/8/19.\n// Copyright (c) 2015 ibireme. A"
},
{
"path": "Demo/YYTextDemo/YYTextBindingExample.h",
"chars": 223,
"preview": "//\n// YYTextBindingExample.h\n// YYKitExample\n//\n// Created by ibireme on 15/9/3.\n// Copyright (c) 2015 ibireme. All "
},
{
"path": "Demo/YYTextDemo/YYTextBindingExample.m",
"chars": 3978,
"preview": "//\n// YYTextBindingExample.m\n// YYKitExample\n//\n// Created by ibireme on 15/9/3.\n// Copyright (c) 2015 ibireme. All "
},
{
"path": "Demo/YYTextDemo/YYTextCopyPasteExample.h",
"chars": 228,
"preview": "//\n// YYTextCopyPasteExample.h\n// YYKitExample\n//\n// Created by ibireme on 15/9/12.\n// Copyright (c) 2015 ibireme. A"
},
{
"path": "Demo/YYTextDemo/YYTextCopyPasteExample.m",
"chars": 2600,
"preview": "//\n// YYTextCopyPasteExample.m\n// YYKitExample\n//\n// Created by ibireme on 15/9/12.\n// Copyright (c) 2015 ibireme. A"
},
{
"path": "Demo/YYTextDemo/YYTextEditExample.h",
"chars": 217,
"preview": "//\n// YYTextEditExample.h\n// YYKitExample\n//\n// Created by ibireme on 15/9/3.\n// Copyright (c) 2015 ibireme. All rig"
},
{
"path": "Demo/YYTextDemo/YYTextEditExample.m",
"chars": 8803,
"preview": "//\n// YYTextEditExample.m\n// YYKitExample\n//\n// Created by ibireme on 15/9/3.\n// Copyright (c) 2015 ibireme. All rig"
},
{
"path": "Demo/YYTextDemo/YYTextEmoticonExample.h",
"chars": 225,
"preview": "//\n// YYTextEmoticonExample.h\n// YYKitExample\n//\n// Created by ibireme on 15/9/3.\n// Copyright (c) 2015 ibireme. All"
},
{
"path": "Demo/YYTextDemo/YYTextEmoticonExample.m",
"chars": 3443,
"preview": "//\n// YYTextEmoticonExample.m\n// YYKitExample\n//\n// Created by ibireme on 15/9/3.\n// Copyright (c) 2015 ibireme. All"
},
{
"path": "Demo/YYTextDemo/YYTextExample.h",
"chars": 215,
"preview": "//\n// YYTextExample.h\n// YYKitExample\n//\n// Created by ibireme on 15/7/18.\n// Copyright (c) 2015 ibireme. All rights"
},
{
"path": "Demo/YYTextDemo/YYTextExample.m",
"chars": 2492,
"preview": "//\n// YYTextExample.m\n// YYKitExample\n//\n// Created by ibireme on 15/7/18.\n// Copyright (c) 2015 ibireme. All rights"
},
{
"path": "Demo/YYTextDemo/YYTextExampleHelper.h",
"chars": 323,
"preview": "//\n// YYTextExampleHelper.h\n// YYKitExample\n//\n// Created by ibireme on 15/9/3.\n// Copyright (c) 2015 ibireme. All r"
},
{
"path": "Demo/YYTextDemo/YYTextExampleHelper.m",
"chars": 1616,
"preview": "//\n// YYTextExampleHelper.m\n// YYKitExample\n//\n// Created by ibireme on 15/9/3.\n// Copyright (c) 2015 ibireme. All r"
},
{
"path": "Demo/YYTextDemo/YYTextMarkdownExample.h",
"chars": 225,
"preview": "//\n// YYTextMarkdownExample.h\n// YYKitExample\n//\n// Created by ibireme on 15/9/3.\n// Copyright (c) 2015 ibireme. All"
},
{
"path": "Demo/YYTextDemo/YYTextMarkdownExample.m",
"chars": 2839,
"preview": "//\n// YYTextMarkdownExample.m\n// YYKitExample\n//\n// Created by ibireme on 15/9/3.\n// Copyright (c) 2015 ibireme. All"
},
{
"path": "Demo/YYTextDemo/YYTextRubyExample.h",
"chars": 217,
"preview": "//\n// YYTextRubyExample.h\n// YYKitExample\n//\n// Created by ibireme on 15/9/9.\n// Copyright (C) 2015 ibireme. All rig"
},
{
"path": "Demo/YYTextDemo/YYTextRubyExample.m",
"chars": 3762,
"preview": "//\n// YYTextRubyExample.m\n// YYKitExample\n//\n// Created by ibireme on 15/9/9.\n// Copyright (C) 2015 ibireme. All rig"
},
{
"path": "Demo/YYTextDemo/YYTextTagExample.h",
"chars": 216,
"preview": "//\n// YYTextTagExample.h\n// YYKitExample\n//\n// Created by ibireme on 15/8/19.\n// Copyright (c) 2015 ibireme. All rig"
},
{
"path": "Demo/YYTextDemo/YYTextTagExample.m",
"chars": 4166,
"preview": "//\n// YYTextTagExample.m\n// YYKitExample\n//\n// Created by ibireme on 15/8/19.\n// Copyright (c) 2015 ibireme. All rig"
},
{
"path": "Demo/YYTextDemo/YYTextUndoRedoExample.h",
"chars": 226,
"preview": "//\n// YYTextUndoRedoExample.h\n// YYKitExample\n//\n// Created by ibireme on 15/9/12.\n// Copyright (c) 2015 ibireme. Al"
},
{
"path": "Demo/YYTextDemo/YYTextUndoRedoExample.m",
"chars": 2334,
"preview": "//\n// YYTextUndoRedoExample.m\n// YYKitExample\n//\n// Created by ibireme on 15/9/12.\n// Copyright (c) 2015 ibireme. Al"
},
{
"path": "Demo/YYTextDemo/YYWeakProxy.h",
"chars": 1227,
"preview": "//\n// YYWeakProxy.h\n// YYKit <https://github.com/ibireme/YYKit>\n//\n// Created by ibireme on 14/10/18.\n// Copyright ("
},
{
"path": "Demo/YYTextDemo/YYWeakProxy.m",
"chars": 1642,
"preview": "//\n// YYWeakProxy.m\n// YYKit <https://github.com/ibireme/YYKit>\n//\n// Created by ibireme on 14/10/18.\n// Copyright ("
},
{
"path": "Demo/YYTextDemo/main.m",
"chars": 331,
"preview": "//\n// main.m\n// YYTextDemo\n//\n// Created by ibireme on 15/10/17.\n// Copyright © 2015年 ibireme. All rights reserved.\n"
},
{
"path": "Demo/YYTextDemo.xcodeproj/project.pbxproj",
"chars": 57974,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 48;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "Demo/YYTextDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 155,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:YYTextDemo.xcod"
},
{
"path": "Framework/Info.plist",
"chars": 776,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Framework/YYText.xcodeproj/project.pbxproj",
"chars": 37084,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 48;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "Framework/YYText.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 151,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:YYText.xcodepro"
},
{
"path": "Framework/YYText.xcodeproj/xcshareddata/xcschemes/YYText.xcscheme",
"chars": 2841,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"0830\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "LICENSE",
"chars": 1095,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2015 ibireme <ibireme@gmail.com>\n\nPermission is hereby granted, free of charge, to "
},
{
"path": "README.md",
"chars": 38501,
"preview": "YYText\n==============\n[](https://raw.github"
},
{
"path": "YYText/Component/YYTextContainerView.h",
"chars": 1817,
"preview": "//\n// YYTextContainerView.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/4/21.\n// Co"
},
{
"path": "YYText/Component/YYTextContainerView.m",
"chars": 4647,
"preview": "//\n// YYTextContainerView.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/4/21.\n// Co"
},
{
"path": "YYText/Component/YYTextDebugOption.h",
"chars": 3267,
"preview": "//\n// YYTextDebugOption.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/4/8.\n// Copyr"
},
{
"path": "YYText/Component/YYTextDebugOption.m",
"chars": 4312,
"preview": "//\n// YYTextDebugOption.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/4/8.\n// Copyr"
},
{
"path": "YYText/Component/YYTextEffectWindow.h",
"chars": 1630,
"preview": "//\n// YYTextEffectWindow.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/2/25.\n// Cop"
},
{
"path": "YYText/Component/YYTextEffectWindow.m",
"chars": 18759,
"preview": "//\n// YYTextEffectWindow.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/2/25.\n// Cop"
},
{
"path": "YYText/Component/YYTextInput.h",
"chars": 2901,
"preview": "//\n// YYTextInput.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/4/17.\n// Copyright "
},
{
"path": "YYText/Component/YYTextInput.m",
"chars": 4257,
"preview": "//\n// YYTextInput.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/4/17.\n// Copyright "
},
{
"path": "YYText/Component/YYTextKeyboardManager.h",
"chars": 3126,
"preview": "//\n// YYTextKeyboardManager.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/6/3.\n// C"
},
{
"path": "YYText/Component/YYTextKeyboardManager.m",
"chars": 18144,
"preview": "//\n// YYTextKeyboardManager.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/6/3.\n// C"
},
{
"path": "YYText/Component/YYTextLayout.h",
"chars": 21566,
"preview": "//\n// YYTextLayout.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/3/3.\n// Copyright "
},
{
"path": "YYText/Component/YYTextLayout.m",
"chars": 152721,
"preview": "//\n// YYTextLayout.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/3/3.\n// Copyright "
},
{
"path": "YYText/Component/YYTextLine.h",
"chars": 3244,
"preview": "//\n// YYTextLine.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/3/10.\n// Copyright ("
},
{
"path": "YYText/Component/YYTextLine.m",
"chars": 5516,
"preview": "//\n// YYYTextLine.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/3/3.\n// Copyright ("
},
{
"path": "YYText/Component/YYTextMagnifier.h",
"chars": 2032,
"preview": "//\n// YYTextMagnifier.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/2/25.\n// Copyri"
},
{
"path": "YYText/Component/YYTextMagnifier.m",
"chars": 12716,
"preview": "//\n// YYTextMagnifier.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/2/25.\n// Copyri"
},
{
"path": "YYText/Component/YYTextSelectionView.h",
"chars": 2529,
"preview": "//\n// YYTextSelectionView.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/2/25.\n// Co"
},
{
"path": "YYText/Component/YYTextSelectionView.m",
"chars": 11303,
"preview": "//\n// YYTextSelectionView.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/2/25.\n// Co"
},
{
"path": "YYText/String/YYTextArchiver.h",
"chars": 920,
"preview": "//\n// YYTextArchiver.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/3/16.\n// Copyrig"
},
{
"path": "YYText/String/YYTextArchiver.m",
"chars": 7115,
"preview": "//\n// YYTextArchiver.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/3/16.\n// Copyrig"
},
{
"path": "YYText/String/YYTextAttribute.h",
"chars": 15205,
"preview": "//\n// YYTextAttribute.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 14/10/26.\n// Copyr"
},
{
"path": "YYText/String/YYTextAttribute.m",
"chars": 17851,
"preview": "//\n// YYTextAttribute.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 14/10/26.\n// Copyr"
},
{
"path": "YYText/String/YYTextParser.h",
"chars": 3122,
"preview": "//\n// YYTextParser.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/3/6.\n// Copyright "
},
{
"path": "YYText/String/YYTextParser.m",
"chars": 20732,
"preview": "//\n// YYTextParser.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/3/6.\n// Copyright "
},
{
"path": "YYText/String/YYTextRubyAnnotation.h",
"chars": 2376,
"preview": "//\n// YYTextRubyAnnotation.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/4/24.\n// C"
},
{
"path": "YYText/String/YYTextRubyAnnotation.m",
"chars": 3379,
"preview": "//\n// YYTextRubyAnnotation.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/4/24.\n// C"
},
{
"path": "YYText/String/YYTextRunDelegate.h",
"chars": 1587,
"preview": "//\n// YYTextRunDelegate.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 14/10/14.\n// Cop"
},
{
"path": "YYText/String/YYTextRunDelegate.m",
"chars": 2206,
"preview": "//\n// YYTextRunDelegate.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 14/10/14.\n// Cop"
},
{
"path": "YYText/Utility/NSAttributedString+YYText.h",
"chars": 58488,
"preview": "//\n// NSAttributedString+YYText.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 14/10/7.\n"
},
{
"path": "YYText/Utility/NSAttributedString+YYText.m",
"chars": 48793,
"preview": "//\n// NSAttributedString+YYText.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 14/10/7.\n"
},
{
"path": "YYText/Utility/NSParagraphStyle+YYText.h",
"chars": 871,
"preview": "//\n// NSParagraphStyle+YYText.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 14/10/7.\n//"
},
{
"path": "YYText/Utility/NSParagraphStyle+YYText.m",
"chars": 9509,
"preview": "//\n// NSParagraphStyle+YYText.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 14/10/7.\n//"
},
{
"path": "YYText/Utility/UIPasteboard+YYText.h",
"chars": 1405,
"preview": "//\n// UIPasteboard+YYText.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/4/2.\n// Cop"
},
{
"path": "YYText/Utility/UIPasteboard+YYText.m",
"chars": 4832,
"preview": "//\n// UIPasteboard+YYText.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/4/2.\n// Cop"
},
{
"path": "YYText/Utility/UIView+YYText.h",
"chars": 2601,
"preview": "//\n// UIView+YYText.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 13/4/3.\n// Copyright"
},
{
"path": "YYText/Utility/UIView+YYText.m",
"chars": 4158,
"preview": "//\n// UIView+YYText.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 13/4/3.\n// Copyright"
},
{
"path": "YYText/Utility/YYTextAsyncLayer.h",
"chars": 2481,
"preview": "//\n// YYTextAsyncLayer.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/4/11.\n// Copyr"
},
{
"path": "YYText/Utility/YYTextAsyncLayer.m",
"chars": 8321,
"preview": "//\n// YYTextAsyncLayer.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/4/11.\n// Copyr"
},
{
"path": "YYText/Utility/YYTextTransaction.h",
"chars": 1143,
"preview": "//\n// YYTextTransaction.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/4/18.\n// Copy"
},
{
"path": "YYText/Utility/YYTextTransaction.m",
"chars": 2509,
"preview": "//\n// YYTextTransaction.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/4/18.\n// Copy"
},
{
"path": "YYText/Utility/YYTextUtilities.h",
"chars": 18687,
"preview": "//\n// YYTextUtilities.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/4/6.\n// Copyrig"
},
{
"path": "YYText/Utility/YYTextUtilities.m",
"chars": 13333,
"preview": "//\n// YYTextUtilities.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/4/6.\n// Copyrig"
},
{
"path": "YYText/Utility/YYTextWeakProxy.h",
"chars": 1303,
"preview": "//\n// YYTextWeakProxy.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 14/10/18.\n// Copyr"
},
{
"path": "YYText/Utility/YYTextWeakProxy.m",
"chars": 1660,
"preview": "//\n// YYTextWeakProxy.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 14/10/18.\n// Copyr"
},
{
"path": "YYText/YYLabel.h",
"chars": 14731,
"preview": "//\n// YYLabel.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/2/25.\n// Copyright (c) "
},
{
"path": "YYText/YYLabel.m",
"chars": 47776,
"preview": "//\n// YYLabel.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/2/25.\n// Copyright (c) "
},
{
"path": "YYText/YYText.h",
"chars": 1480,
"preview": "//\n// YYText.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/2/25.\n// Copyright (c) 2"
},
{
"path": "YYText/YYTextView.h",
"chars": 17249,
"preview": "//\n// YYTextView.h\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/2/25.\n// Copyright ("
},
{
"path": "YYText/YYTextView.m",
"chars": 155453,
"preview": "//\n// YYTextView.m\n// YYText <https://github.com/ibireme/YYText>\n//\n// Created by ibireme on 15/2/25.\n// Copyright ("
},
{
"path": "YYText.podspec",
"chars": 760,
"preview": "Pod::Spec.new do |s|\n s.name = 'YYText'\n s.summary = 'Powerful text framework for iOS to display and edit"
}
]
About this extraction
This page contains the full source code of the ibireme/YYText GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 129 files (1.2 MB), approximately 327.7k tokens, and a symbol index with 52 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.