页面绘制
This commit is contained in:
parent
b3d4ea752c
commit
975311e9e6
BIN
ProductApp.zip
BIN
ProductApp.zip
Binary file not shown.
|
@ -24,6 +24,8 @@ target 'ProductApp' do
|
|||
|
||||
pod 'SSZipArchive'
|
||||
|
||||
pod 'YYText'
|
||||
|
||||
#pod 'AMapNavi'
|
||||
#pod 'AMapLocation'
|
||||
#pod 'AMapSearch'
|
||||
|
|
|
@ -32,6 +32,7 @@ PODS:
|
|||
- SSZipArchive (2.4.3)
|
||||
- "UITableView+FDTemplateLayoutCell (1.6)"
|
||||
- YYModel (1.0.4)
|
||||
- YYText (1.0.7)
|
||||
|
||||
DEPENDENCIES:
|
||||
- AFNetworking
|
||||
|
@ -50,6 +51,7 @@ DEPENDENCIES:
|
|||
- SSZipArchive
|
||||
- "UITableView+FDTemplateLayoutCell"
|
||||
- YYModel
|
||||
- YYText
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
|
@ -69,9 +71,10 @@ SPEC REPOS:
|
|||
- SSZipArchive
|
||||
- "UITableView+FDTemplateLayoutCell"
|
||||
- YYModel
|
||||
- YYText
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce
|
||||
AFNetworking: 3bd23d814e976cd148d7d44c3ab78017b744cd58
|
||||
DZNEmptyDataSet: 9525833b9e68ac21c30253e1d3d7076cc828eaa7
|
||||
IQKeyboardManager: 45a1fa55c1a5b02c61ac0fd7fd5b62bb4ad20d97
|
||||
LSTTimer: caf8f02ff366ca175cf4c1778d26c166183c1b6f
|
||||
|
@ -87,7 +90,8 @@ SPEC CHECKSUMS:
|
|||
SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef
|
||||
"UITableView+FDTemplateLayoutCell": 5c949b4a5059c404b442926c0e80f81d10a2d66f
|
||||
YYModel: 2a7fdd96aaa4b86a824e26d0c517de8928c04b30
|
||||
YYText: 5c461d709e24d55a182d1441c41dc639a18a4849
|
||||
|
||||
PODFILE CHECKSUM: 9e63afc3a6c622d807df578d0a72b7f55bd6d3f4
|
||||
PODFILE CHECKSUM: 3a3f16ff03dd509a5468a270e3ff3de69a081d1e
|
||||
|
||||
COCOAPODS: 1.12.1
|
||||
COCOAPODS: 1.15.2
|
||||
|
|
|
@ -32,6 +32,7 @@ PODS:
|
|||
- SSZipArchive (2.4.3)
|
||||
- "UITableView+FDTemplateLayoutCell (1.6)"
|
||||
- YYModel (1.0.4)
|
||||
- YYText (1.0.7)
|
||||
|
||||
DEPENDENCIES:
|
||||
- AFNetworking
|
||||
|
@ -50,6 +51,7 @@ DEPENDENCIES:
|
|||
- SSZipArchive
|
||||
- "UITableView+FDTemplateLayoutCell"
|
||||
- YYModel
|
||||
- YYText
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
|
@ -69,9 +71,10 @@ SPEC REPOS:
|
|||
- SSZipArchive
|
||||
- "UITableView+FDTemplateLayoutCell"
|
||||
- YYModel
|
||||
- YYText
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce
|
||||
AFNetworking: 3bd23d814e976cd148d7d44c3ab78017b744cd58
|
||||
DZNEmptyDataSet: 9525833b9e68ac21c30253e1d3d7076cc828eaa7
|
||||
IQKeyboardManager: 45a1fa55c1a5b02c61ac0fd7fd5b62bb4ad20d97
|
||||
LSTTimer: caf8f02ff366ca175cf4c1778d26c166183c1b6f
|
||||
|
@ -87,7 +90,8 @@ SPEC CHECKSUMS:
|
|||
SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef
|
||||
"UITableView+FDTemplateLayoutCell": 5c949b4a5059c404b442926c0e80f81d10a2d66f
|
||||
YYModel: 2a7fdd96aaa4b86a824e26d0c517de8928c04b30
|
||||
YYText: 5c461d709e24d55a182d1441c41dc639a18a4849
|
||||
|
||||
PODFILE CHECKSUM: 9e63afc3a6c622d807df578d0a72b7f55bd6d3f4
|
||||
PODFILE CHECKSUM: 3a3f16ff03dd509a5468a270e3ff3de69a081d1e
|
||||
|
||||
COCOAPODS: 1.12.1
|
||||
COCOAPODS: 1.15.2
|
||||
|
|
File diff suppressed because it is too large
Load Diff
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/AFNetworking.xcscheme
generated
Normal file
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/AFNetworking.xcscheme
generated
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0130B3724283586C0E9D2A112D4F2AA1"
|
||||
BuildableName = "AFNetworking.framework"
|
||||
BlueprintName = "AFNetworking"
|
||||
ReferencedContainer = "container:Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/DZNEmptyDataSet.xcscheme
generated
Normal file
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/DZNEmptyDataSet.xcscheme
generated
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "F1BCD9702276377FB5B3BDB6EAF709D7"
|
||||
BuildableName = "DZNEmptyDataSet.framework"
|
||||
BlueprintName = "DZNEmptyDataSet"
|
||||
ReferencedContainer = "container:Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/IQKeyboardManager.xcscheme
generated
Normal file
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/IQKeyboardManager.xcscheme
generated
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "FBA456CB50E371584C11231929A0971E"
|
||||
BuildableName = "IQKeyboardManager.framework"
|
||||
BlueprintName = "IQKeyboardManager"
|
||||
ReferencedContainer = "container:Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/LSTTimer.xcscheme
generated
Normal file
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/LSTTimer.xcscheme
generated
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "5A2609DB0612F95A3450D2E02CA850A0"
|
||||
BuildableName = "LSTTimer.framework"
|
||||
BlueprintName = "LSTTimer"
|
||||
ReferencedContainer = "container:Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/MBProgressHUD.xcscheme
generated
Normal file
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/MBProgressHUD.xcscheme
generated
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "82B0A41D3031FF27D78E17B0A9A46FB0"
|
||||
BuildableName = "MBProgressHUD.framework"
|
||||
BlueprintName = "MBProgressHUD"
|
||||
ReferencedContainer = "container:Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/MJRefresh.xcscheme
generated
Normal file
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/MJRefresh.xcscheme
generated
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6868056D761E163D10FDAF8CF1C4D9B8"
|
||||
BuildableName = "MJRefresh.framework"
|
||||
BlueprintName = "MJRefresh"
|
||||
ReferencedContainer = "container:Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/MOFSPickerManager.xcscheme
generated
Normal file
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/MOFSPickerManager.xcscheme
generated
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "9B407B312D9EAE176A2DDE572135AC35"
|
||||
BuildableName = "MOFSPickerManager.framework"
|
||||
BlueprintName = "MOFSPickerManager"
|
||||
ReferencedContainer = "container:Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/Masonry.xcscheme
generated
Normal file
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/Masonry.xcscheme
generated
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "55AF53E6C77A10ED4985E04D74A8878E"
|
||||
BuildableName = "Masonry.framework"
|
||||
BlueprintName = "Masonry"
|
||||
ReferencedContainer = "container:Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/Pods-ProductApp.xcscheme
generated
Normal file
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/Pods-ProductApp.xcscheme
generated
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "C350D0AF2821ACA667D8ABD5B11F720F"
|
||||
BuildableName = "Pods_ProductApp.framework"
|
||||
BlueprintName = "Pods-ProductApp"
|
||||
ReferencedContainer = "container:Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/PopupKit.xcscheme
generated
Normal file
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/PopupKit.xcscheme
generated
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "498769A9CE4D8F28BB9BCA14EDF3ADEE"
|
||||
BuildableName = "PopupKit.framework"
|
||||
BlueprintName = "PopupKit"
|
||||
ReferencedContainer = "container:Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/Reachability.xcscheme
generated
Normal file
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/Reachability.xcscheme
generated
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "CAA047C0F5E4106F3904E8497FA17F97"
|
||||
BuildableName = "Reachability.framework"
|
||||
BlueprintName = "Reachability"
|
||||
ReferencedContainer = "container:Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/SDAutoLayout.xcscheme
generated
Normal file
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/SDAutoLayout.xcscheme
generated
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "03932C47FBFD347205B2F367F78E9B63"
|
||||
BuildableName = "SDAutoLayout.framework"
|
||||
BlueprintName = "SDAutoLayout"
|
||||
ReferencedContainer = "container:Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/SDCycleScrollView.xcscheme
generated
Normal file
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/SDCycleScrollView.xcscheme
generated
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "CBED833AAD6266F3AEFE9BE31C68E094"
|
||||
BuildableName = "SDCycleScrollView.framework"
|
||||
BlueprintName = "SDCycleScrollView"
|
||||
ReferencedContainer = "container:Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/SDWebImage.xcscheme
generated
Normal file
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/SDWebImage.xcscheme
generated
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "3847153A6E5EEFB86565BA840768F429"
|
||||
BuildableName = "SDWebImage.framework"
|
||||
BlueprintName = "SDWebImage"
|
||||
ReferencedContainer = "container:Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/SSZipArchive.xcscheme
generated
Normal file
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/SSZipArchive.xcscheme
generated
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "F60E38364AFF5E1349FF07415B944396"
|
||||
BuildableName = "SSZipArchive.framework"
|
||||
BlueprintName = "SSZipArchive"
|
||||
ReferencedContainer = "container:Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2006263064A94C0063192AE0D2C3F409"
|
||||
BuildableName = "UITableView_FDTemplateLayoutCell.framework"
|
||||
BlueprintName = "UITableView+FDTemplateLayoutCell"
|
||||
ReferencedContainer = "container:Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/YYModel.xcscheme
generated
Normal file
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/YYModel.xcscheme
generated
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "84B44807A12996D487A4A591A481D6A0"
|
||||
BuildableName = "YYModel.framework"
|
||||
BlueprintName = "YYModel"
|
||||
ReferencedContainer = "container:Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/YYText.xcscheme
generated
Normal file
58
ProductApp/Pods/Pods.xcodeproj/xcuserdata/gongzuo.xcuserdatad/xcschemes/YYText.xcscheme
generated
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1500"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "6F5C17E80BD1B7F60A33F79692330A57"
|
||||
BuildableName = "YYText.framework"
|
||||
BlueprintName = "YYText"
|
||||
ReferencedContainer = "container:Pods.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -4,91 +4,98 @@
|
|||
<dict>
|
||||
<key>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>AFNetworking.xcscheme_^#shared#^_</key>
|
||||
<key>AFNetworking.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>5</integer>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>DZNEmptyDataSet.xcscheme_^#shared#^_</key>
|
||||
<key>DZNEmptyDataSet.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>4</integer>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>IQKeyboardManager.xcscheme_^#shared#^_</key>
|
||||
<key>IQKeyboardManager.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>17</integer>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>LSTTimer.xcscheme_^#shared#^_</key>
|
||||
<key>LSTTimer.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>15</integer>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>MBProgressHUD.xcscheme_^#shared#^_</key>
|
||||
<key>MBProgressHUD.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>2</integer>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>MJRefresh.xcscheme_^#shared#^_</key>
|
||||
<key>MJRefresh.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>19</integer>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>MOFSPickerManager.xcscheme_^#shared#^_</key>
|
||||
<key>MOFSPickerManager.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>11</integer>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>Masonry.xcscheme_^#shared#^_</key>
|
||||
<key>Masonry.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>13</integer>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>Pods-ProductApp.xcscheme_^#shared#^_</key>
|
||||
<key>Pods-ProductApp.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>16</integer>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>PopupKit.xcscheme_^#shared#^_</key>
|
||||
<key>PopupKit.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>7</integer>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>Reachability.xcscheme_^#shared#^_</key>
|
||||
<key>Reachability.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>18</integer>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>SDAutoLayout.xcscheme_^#shared#^_</key>
|
||||
<key>SDAutoLayout.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>3</integer>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>SDCycleScrollView.xcscheme_^#shared#^_</key>
|
||||
<key>SDCycleScrollView.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>10</integer>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>SDWebImage.xcscheme_^#shared#^_</key>
|
||||
<key>SDWebImage.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>12</integer>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>SSZipArchive.xcscheme_^#shared#^_</key>
|
||||
<key>SSZipArchive.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>6</integer>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>UITableView+FDTemplateLayoutCell.xcscheme_^#shared#^_</key>
|
||||
<key>UITableView+FDTemplateLayoutCell.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>14</integer>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>YYModel.xcscheme_^#shared#^_</key>
|
||||
<key>YYModel.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>9</integer>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>YYText.xcscheme</key>
|
||||
<dict>
|
||||
<key>isShown</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
<dict/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -366,4 +366,30 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||
SOFTWARE.
|
||||
|
||||
|
||||
|
||||
## YYText
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 ibireme <ibireme@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
Generated by CocoaPods - https://cocoapods.org
|
||||
|
|
|
@ -474,6 +474,38 @@ SOFTWARE.
|
|||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 ibireme <ibireme@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
</string>
|
||||
<key>License</key>
|
||||
<string>MIT</string>
|
||||
<key>Title</key>
|
||||
<string>YYText</string>
|
||||
<key>Type</key>
|
||||
<string>PSGroupSpecifier</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>FooterText</key>
|
||||
<string>Generated by CocoaPods - https://cocoapods.org</string>
|
||||
|
|
|
@ -14,4 +14,5 @@ ${BUILT_PRODUCTS_DIR}/SDCycleScrollView/SDCycleScrollView.framework
|
|||
${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework
|
||||
${BUILT_PRODUCTS_DIR}/SSZipArchive/SSZipArchive.framework
|
||||
${BUILT_PRODUCTS_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework
|
||||
${BUILT_PRODUCTS_DIR}/YYModel/YYModel.framework
|
||||
${BUILT_PRODUCTS_DIR}/YYModel/YYModel.framework
|
||||
${BUILT_PRODUCTS_DIR}/YYText/YYText.framework
|
|
@ -13,4 +13,5 @@ ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDCycleScrollView.framework
|
|||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SSZipArchive.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/UITableView_FDTemplateLayoutCell.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYModel.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYModel.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYText.framework
|
|
@ -14,4 +14,5 @@ ${BUILT_PRODUCTS_DIR}/SDCycleScrollView/SDCycleScrollView.framework
|
|||
${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework
|
||||
${BUILT_PRODUCTS_DIR}/SSZipArchive/SSZipArchive.framework
|
||||
${BUILT_PRODUCTS_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework
|
||||
${BUILT_PRODUCTS_DIR}/YYModel/YYModel.framework
|
||||
${BUILT_PRODUCTS_DIR}/YYModel/YYModel.framework
|
||||
${BUILT_PRODUCTS_DIR}/YYText/YYText.framework
|
|
@ -13,4 +13,5 @@ ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDCycleScrollView.framework
|
|||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SSZipArchive.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/UITableView_FDTemplateLayoutCell.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYModel.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYModel.framework
|
||||
${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYText.framework
|
|
@ -18,7 +18,7 @@ echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
|||
mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
|
||||
|
||||
COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}"
|
||||
SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
|
||||
SWIFT_STDLIB_PATH="${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
|
||||
BCSYMBOLMAP_DIR="BCSymbolMaps"
|
||||
|
||||
|
||||
|
@ -192,6 +192,7 @@ if [[ "$CONFIGURATION" == "Debug" ]]; then
|
|||
install_framework "${BUILT_PRODUCTS_DIR}/SSZipArchive/SSZipArchive.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/YYModel/YYModel.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/YYText/YYText.framework"
|
||||
fi
|
||||
if [[ "$CONFIGURATION" == "Release" ]]; then
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework"
|
||||
|
@ -210,6 +211,7 @@ if [[ "$CONFIGURATION" == "Release" ]]; then
|
|||
install_framework "${BUILT_PRODUCTS_DIR}/SSZipArchive/SSZipArchive.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/YYModel/YYModel.framework"
|
||||
install_framework "${BUILT_PRODUCTS_DIR}/YYText/YYText.framework"
|
||||
fi
|
||||
if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
|
||||
wait
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DZNEmptyDataSet" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManager" "${PODS_CONFIGURATION_BUILD_DIR}/LSTTimer" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/MOFSPickerManager" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/PopupKit" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability" "${PODS_CONFIGURATION_BUILD_DIR}/SDAutoLayout" "${PODS_CONFIGURATION_BUILD_DIR}/SDCycleScrollView" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SSZipArchive" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel"
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DZNEmptyDataSet" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManager" "${PODS_CONFIGURATION_BUILD_DIR}/LSTTimer" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/MOFSPickerManager" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/PopupKit" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability" "${PODS_CONFIGURATION_BUILD_DIR}/SDAutoLayout" "${PODS_CONFIGURATION_BUILD_DIR}/SDCycleScrollView" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SSZipArchive" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel" "${PODS_CONFIGURATION_BUILD_DIR}/YYText"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DZNEmptyDataSet/DZNEmptyDataSet.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManager/IQKeyboardManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/LSTTimer/LSTTimer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MOFSPickerManager/MOFSPickerManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/PopupKit/PopupKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDAutoLayout/SDAutoLayout.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDCycleScrollView/SDCycleScrollView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SSZipArchive/SSZipArchive.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel/YYModel.framework/Headers"
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DZNEmptyDataSet/DZNEmptyDataSet.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManager/IQKeyboardManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/LSTTimer/LSTTimer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MOFSPickerManager/MOFSPickerManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/PopupKit/PopupKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDAutoLayout/SDAutoLayout.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDCycleScrollView/SDCycleScrollView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SSZipArchive/SSZipArchive.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel/YYModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYText/YYText.framework/Headers"
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_LDFLAGS = $(inherited) -l"iconv" -l"z" -framework "AFNetworking" -framework "CoreFoundation" -framework "CoreGraphics" -framework "DZNEmptyDataSet" -framework "Foundation" -framework "IQKeyboardManager" -framework "ImageIO" -framework "LSTTimer" -framework "MBProgressHUD" -framework "MJRefresh" -framework "MOFSPickerManager" -framework "Masonry" -framework "PopupKit" -framework "QuartzCore" -framework "Reachability" -framework "SDAutoLayout" -framework "SDCycleScrollView" -framework "SDWebImage" -framework "SSZipArchive" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -framework "UITableView_FDTemplateLayoutCell" -framework "YYModel"
|
||||
OTHER_LDFLAGS = $(inherited) -l"iconv" -l"z" -framework "AFNetworking" -framework "Accelerate" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreText" -framework "DZNEmptyDataSet" -framework "Foundation" -framework "IQKeyboardManager" -framework "ImageIO" -framework "LSTTimer" -framework "MBProgressHUD" -framework "MJRefresh" -framework "MOFSPickerManager" -framework "Masonry" -framework "MobileCoreServices" -framework "PopupKit" -framework "QuartzCore" -framework "Reachability" -framework "SDAutoLayout" -framework "SDCycleScrollView" -framework "SDWebImage" -framework "SSZipArchive" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -framework "UITableView_FDTemplateLayoutCell" -framework "YYModel" -framework "YYText"
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DZNEmptyDataSet" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManager" "${PODS_CONFIGURATION_BUILD_DIR}/LSTTimer" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/MOFSPickerManager" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/PopupKit" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability" "${PODS_CONFIGURATION_BUILD_DIR}/SDAutoLayout" "${PODS_CONFIGURATION_BUILD_DIR}/SDCycleScrollView" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SSZipArchive" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel"
|
||||
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking" "${PODS_CONFIGURATION_BUILD_DIR}/DZNEmptyDataSet" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManager" "${PODS_CONFIGURATION_BUILD_DIR}/LSTTimer" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh" "${PODS_CONFIGURATION_BUILD_DIR}/MOFSPickerManager" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry" "${PODS_CONFIGURATION_BUILD_DIR}/PopupKit" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability" "${PODS_CONFIGURATION_BUILD_DIR}/SDAutoLayout" "${PODS_CONFIGURATION_BUILD_DIR}/SDCycleScrollView" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage" "${PODS_CONFIGURATION_BUILD_DIR}/SSZipArchive" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel" "${PODS_CONFIGURATION_BUILD_DIR}/YYText"
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DZNEmptyDataSet/DZNEmptyDataSet.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManager/IQKeyboardManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/LSTTimer/LSTTimer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MOFSPickerManager/MOFSPickerManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/PopupKit/PopupKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDAutoLayout/SDAutoLayout.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDCycleScrollView/SDCycleScrollView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SSZipArchive/SSZipArchive.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel/YYModel.framework/Headers"
|
||||
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AFNetworking/AFNetworking.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/DZNEmptyDataSet/DZNEmptyDataSet.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManager/IQKeyboardManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/LSTTimer/LSTTimer.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MBProgressHUD/MBProgressHUD.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MJRefresh/MJRefresh.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/MOFSPickerManager/MOFSPickerManager.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Masonry/Masonry.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/PopupKit/PopupKit.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Reachability/Reachability.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDAutoLayout/SDAutoLayout.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDCycleScrollView/SDCycleScrollView.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SDWebImage/SDWebImage.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/SSZipArchive/SSZipArchive.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/UITableView+FDTemplateLayoutCell/UITableView_FDTemplateLayoutCell.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYModel/YYModel.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/YYText/YYText.framework/Headers"
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
|
||||
OTHER_LDFLAGS = $(inherited) -l"iconv" -l"z" -framework "AFNetworking" -framework "CoreFoundation" -framework "CoreGraphics" -framework "DZNEmptyDataSet" -framework "Foundation" -framework "IQKeyboardManager" -framework "ImageIO" -framework "LSTTimer" -framework "MBProgressHUD" -framework "MJRefresh" -framework "MOFSPickerManager" -framework "Masonry" -framework "PopupKit" -framework "QuartzCore" -framework "Reachability" -framework "SDAutoLayout" -framework "SDCycleScrollView" -framework "SDWebImage" -framework "SSZipArchive" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -framework "UITableView_FDTemplateLayoutCell" -framework "YYModel"
|
||||
OTHER_LDFLAGS = $(inherited) -l"iconv" -l"z" -framework "AFNetworking" -framework "Accelerate" -framework "CoreFoundation" -framework "CoreGraphics" -framework "CoreText" -framework "DZNEmptyDataSet" -framework "Foundation" -framework "IQKeyboardManager" -framework "ImageIO" -framework "LSTTimer" -framework "MBProgressHUD" -framework "MJRefresh" -framework "MOFSPickerManager" -framework "Masonry" -framework "MobileCoreServices" -framework "PopupKit" -framework "QuartzCore" -framework "Reachability" -framework "SDAutoLayout" -framework "SDCycleScrollView" -framework "SDWebImage" -framework "SSZipArchive" -framework "Security" -framework "SystemConfiguration" -framework "UIKit" -framework "UITableView_FDTemplateLayoutCell" -framework "YYModel" -framework "YYText"
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<?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>${PODS_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>CFBundleName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.7</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${CURRENT_PROJECT_VERSION}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,5 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
@interface PodsDummy_YYText : NSObject
|
||||
@end
|
||||
@implementation PodsDummy_YYText
|
||||
@end
|
|
@ -0,0 +1,12 @@
|
|||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#ifndef FOUNDATION_EXPORT
|
||||
#if defined(__cplusplus)
|
||||
#define FOUNDATION_EXPORT extern "C"
|
||||
#else
|
||||
#define FOUNDATION_EXPORT extern
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
#ifdef __OBJC__
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
#ifndef FOUNDATION_EXPORT
|
||||
#if defined(__cplusplus)
|
||||
#define FOUNDATION_EXPORT extern "C"
|
||||
#else
|
||||
#define FOUNDATION_EXPORT extern
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#import "YYTextContainerView.h"
|
||||
#import "YYTextDebugOption.h"
|
||||
#import "YYTextEffectWindow.h"
|
||||
#import "YYTextInput.h"
|
||||
#import "YYTextKeyboardManager.h"
|
||||
#import "YYTextLayout.h"
|
||||
#import "YYTextLine.h"
|
||||
#import "YYTextMagnifier.h"
|
||||
#import "YYTextSelectionView.h"
|
||||
#import "YYTextArchiver.h"
|
||||
#import "YYTextAttribute.h"
|
||||
#import "YYTextParser.h"
|
||||
#import "YYTextRubyAnnotation.h"
|
||||
#import "YYTextRunDelegate.h"
|
||||
#import "NSAttributedString+YYText.h"
|
||||
#import "NSParagraphStyle+YYText.h"
|
||||
#import "UIPasteboard+YYText.h"
|
||||
#import "UIView+YYText.h"
|
||||
#import "YYTextAsyncLayer.h"
|
||||
#import "YYTextTransaction.h"
|
||||
#import "YYTextUtilities.h"
|
||||
#import "YYTextWeakProxy.h"
|
||||
#import "YYLabel.h"
|
||||
#import "YYText.h"
|
||||
#import "YYTextView.h"
|
||||
|
||||
FOUNDATION_EXPORT double YYTextVersionNumber;
|
||||
FOUNDATION_EXPORT const unsigned char YYTextVersionString[];
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/YYText
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
OTHER_LDFLAGS = $(inherited) -framework "Accelerate" -framework "CoreFoundation" -framework "CoreText" -framework "MobileCoreServices" -framework "QuartzCore" -framework "UIKit"
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PODS_TARGET_SRCROOT = ${PODS_ROOT}/YYText
|
||||
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
|
||||
SKIP_INSTALL = YES
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
|
@ -0,0 +1,6 @@
|
|||
framework module YYText {
|
||||
umbrella header "YYText-umbrella.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
|
||||
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/YYText
|
||||
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
|
||||
OTHER_LDFLAGS = $(inherited) -framework "Accelerate" -framework "CoreFoundation" -framework "CoreText" -framework "MobileCoreServices" -framework "QuartzCore" -framework "UIKit"
|
||||
PODS_BUILD_DIR = ${BUILD_DIR}
|
||||
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
|
||||
PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
|
||||
PODS_ROOT = ${SRCROOT}
|
||||
PODS_TARGET_SRCROOT = ${PODS_ROOT}/YYText
|
||||
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
|
||||
SKIP_INSTALL = YES
|
||||
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
|
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 ibireme <ibireme@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,55 @@
|
|||
//
|
||||
// YYTextContainerView.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/4/21.
|
||||
// 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(<YYText/YYText.h>)
|
||||
#import <YYText/YYTextLayout.h>
|
||||
#else
|
||||
#import "YYTextLayout.h"
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
A simple view to diaplay `YYTextLayout`.
|
||||
|
||||
@discussion This view can become first responder. If this view is first responder,
|
||||
all the action (such as UIMenu's action) would forward to the `hostView` property.
|
||||
Typically, you should not use this class directly.
|
||||
|
||||
@warning All the methods in this class should be called on main thread.
|
||||
*/
|
||||
@interface YYTextContainerView : UIView
|
||||
|
||||
/// First responder's aciton will forward to this view.
|
||||
@property (nullable, nonatomic, weak) UIView *hostView;
|
||||
|
||||
/// Debug option for layout debug. Set this property will let the view redraw it's contents.
|
||||
@property (nullable, nonatomic, copy) YYTextDebugOption *debugOption;
|
||||
|
||||
/// Text vertical alignment.
|
||||
@property (nonatomic) YYTextVerticalAlignment textVerticalAlignment;
|
||||
|
||||
/// Text layout. Set this property will let the view redraw it's contents.
|
||||
@property (nullable, nonatomic, strong) YYTextLayout *layout;
|
||||
|
||||
/// The contents fade animation duration when the layout's contents changed. Default is 0 (no animation).
|
||||
@property (nonatomic) NSTimeInterval contentsFadeDuration;
|
||||
|
||||
/// Convenience method to set `layout` and `contentsFadeDuration`.
|
||||
/// @param layout Same as `layout` property.
|
||||
/// @param fadeDuration Same as `contentsFadeDuration` property.
|
||||
- (void)setLayout:(nullable YYTextLayout *)layout withFadeDuration:(NSTimeInterval)fadeDuration;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,144 @@
|
|||
//
|
||||
// YYTextContainerView.m
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/4/21.
|
||||
// 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 "YYTextContainerView.h"
|
||||
|
||||
@implementation YYTextContainerView {
|
||||
BOOL _attachmentChanged;
|
||||
NSMutableArray *_attachmentViews;
|
||||
NSMutableArray *_attachmentLayers;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (!self) return nil;
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
_attachmentViews = [NSMutableArray array];
|
||||
_attachmentLayers = [NSMutableArray array];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setDebugOption:(YYTextDebugOption *)debugOption {
|
||||
BOOL needDraw = _debugOption.needDrawDebug;
|
||||
_debugOption = debugOption.copy;
|
||||
if (_debugOption.needDrawDebug != needDraw) {
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setTextVerticalAlignment:(YYTextVerticalAlignment)textVerticalAlignment {
|
||||
if (_textVerticalAlignment == textVerticalAlignment) return;
|
||||
_textVerticalAlignment = textVerticalAlignment;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)setContentsFadeDuration:(NSTimeInterval)contentsFadeDuration {
|
||||
if (_contentsFadeDuration == contentsFadeDuration) return;
|
||||
_contentsFadeDuration = contentsFadeDuration;
|
||||
if (contentsFadeDuration <= 0) {
|
||||
[self.layer removeAnimationForKey:@"contents"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setLayout:(YYTextLayout *)layout {
|
||||
if (_layout == layout) return;
|
||||
_layout = layout;
|
||||
_attachmentChanged = YES;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)setLayout:(YYTextLayout *)layout withFadeDuration:(NSTimeInterval)fadeDuration {
|
||||
self.contentsFadeDuration = fadeDuration;
|
||||
self.layout = layout;
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
// fade content
|
||||
[self.layer removeAnimationForKey:@"contents"];
|
||||
if (_contentsFadeDuration > 0) {
|
||||
CATransition *transition = [CATransition animation];
|
||||
transition.duration = _contentsFadeDuration;
|
||||
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
|
||||
transition.type = kCATransitionFade;
|
||||
[self.layer addAnimation:transition forKey:@"contents"];
|
||||
}
|
||||
|
||||
// update attachment
|
||||
if (_attachmentChanged) {
|
||||
for (UIView *view in _attachmentViews) {
|
||||
if (view.superview == self) [view removeFromSuperview];
|
||||
}
|
||||
for (CALayer *layer in _attachmentLayers) {
|
||||
if (layer.superlayer == self.layer) [layer removeFromSuperlayer];
|
||||
}
|
||||
[_attachmentViews removeAllObjects];
|
||||
[_attachmentLayers removeAllObjects];
|
||||
}
|
||||
|
||||
// draw layout
|
||||
CGSize boundingSize = _layout.textBoundingSize;
|
||||
CGPoint point = CGPointZero;
|
||||
if (_textVerticalAlignment == YYTextVerticalAlignmentCenter) {
|
||||
if (_layout.container.isVerticalForm) {
|
||||
point.x = -(self.bounds.size.width - boundingSize.width) * 0.5;
|
||||
} else {
|
||||
point.y = (self.bounds.size.height - boundingSize.height) * 0.5;
|
||||
}
|
||||
} else if (_textVerticalAlignment == YYTextVerticalAlignmentBottom) {
|
||||
if (_layout.container.isVerticalForm) {
|
||||
point.x = -(self.bounds.size.width - boundingSize.width);
|
||||
} else {
|
||||
point.y = (self.bounds.size.height - boundingSize.height);
|
||||
}
|
||||
}
|
||||
[_layout drawInContext:UIGraphicsGetCurrentContext() size:self.bounds.size point:point view:self layer:self.layer debug:_debugOption cancel:nil];
|
||||
|
||||
// update attachment
|
||||
if (_attachmentChanged) {
|
||||
_attachmentChanged = NO;
|
||||
for (YYTextAttachment *a in _layout.attachments) {
|
||||
if ([a.content isKindOfClass:[UIView class]]) [_attachmentViews addObject:a.content];
|
||||
if ([a.content isKindOfClass:[CALayer class]]) [_attachmentLayers addObject:a.content];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setFrame:(CGRect)frame {
|
||||
CGSize oldSize = self.bounds.size;
|
||||
[super setFrame:frame];
|
||||
if (!CGSizeEqualToSize(oldSize, self.bounds.size)) {
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setBounds:(CGRect)bounds {
|
||||
CGSize oldSize = self.bounds.size;
|
||||
[super setBounds:bounds];
|
||||
if (!CGSizeEqualToSize(oldSize, self.bounds.size)) {
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UIResponder forward
|
||||
|
||||
- (BOOL)canBecomeFirstResponder {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
|
||||
return [self.hostView canPerformAction:action withSender:sender];
|
||||
}
|
||||
|
||||
- (id)forwardingTargetForSelector:(SEL)aSelector {
|
||||
return self.hostView;
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,95 @@
|
|||
//
|
||||
// YYTextDebugOption.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/4/8.
|
||||
// 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>
|
||||
|
||||
@class YYTextDebugOption;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The YYTextDebugTarget protocol defines the method a debug target should implement.
|
||||
A debug target can be add to the global container to receive the shared debug
|
||||
option changed notification.
|
||||
*/
|
||||
@protocol YYTextDebugTarget <NSObject>
|
||||
|
||||
@required
|
||||
/**
|
||||
When the shared debug option changed, this method would be called on main thread.
|
||||
It should return as quickly as possible. The option's property should not be changed
|
||||
in this method.
|
||||
|
||||
@param option The shared debug option.
|
||||
*/
|
||||
- (void)setDebugOption:(nullable YYTextDebugOption *)option;
|
||||
@end
|
||||
|
||||
|
||||
|
||||
/**
|
||||
The debug option for YYText.
|
||||
*/
|
||||
@interface YYTextDebugOption : NSObject <NSCopying>
|
||||
@property (nullable, nonatomic, strong) UIColor *baselineColor; ///< baseline color
|
||||
@property (nullable, nonatomic, strong) UIColor *CTFrameBorderColor; ///< CTFrame path border color
|
||||
@property (nullable, nonatomic, strong) UIColor *CTFrameFillColor; ///< CTFrame path fill color
|
||||
@property (nullable, nonatomic, strong) UIColor *CTLineBorderColor; ///< CTLine bounds border color
|
||||
@property (nullable, nonatomic, strong) UIColor *CTLineFillColor; ///< CTLine bounds fill color
|
||||
@property (nullable, nonatomic, strong) UIColor *CTLineNumberColor; ///< CTLine line number color
|
||||
@property (nullable, nonatomic, strong) UIColor *CTRunBorderColor; ///< CTRun bounds border color
|
||||
@property (nullable, nonatomic, strong) UIColor *CTRunFillColor; ///< CTRun bounds fill color
|
||||
@property (nullable, nonatomic, strong) UIColor *CTRunNumberColor; ///< CTRun number color
|
||||
@property (nullable, nonatomic, strong) UIColor *CGGlyphBorderColor; ///< CGGlyph bounds border color
|
||||
@property (nullable, nonatomic, strong) UIColor *CGGlyphFillColor; ///< CGGlyph bounds fill color
|
||||
|
||||
- (BOOL)needDrawDebug; ///< `YES`: at least one debug color is visible. `NO`: all debug color is invisible/nil.
|
||||
- (void)clear; ///< Set all debug color to nil.
|
||||
|
||||
/**
|
||||
Add a debug target.
|
||||
|
||||
@discussion When `setSharedDebugOption:` is called, all added debug target will
|
||||
receive `setDebugOption:` in main thread. It maintains an unsafe_unretained
|
||||
reference to this target. The target must to removed before dealloc.
|
||||
|
||||
@param target A debug target.
|
||||
*/
|
||||
+ (void)addDebugTarget:(id<YYTextDebugTarget>)target;
|
||||
|
||||
/**
|
||||
Remove a debug target which is added by `addDebugTarget:`.
|
||||
|
||||
@param target A debug target.
|
||||
*/
|
||||
+ (void)removeDebugTarget:(id<YYTextDebugTarget>)target;
|
||||
|
||||
/**
|
||||
Returns the shared debug option.
|
||||
|
||||
@return The shared debug option, default is nil.
|
||||
*/
|
||||
+ (nullable YYTextDebugOption *)sharedDebugOption;
|
||||
|
||||
/**
|
||||
Set a debug option as shared debug option.
|
||||
This method must be called on main thread.
|
||||
|
||||
@discussion When call this method, the new option will set to all debug target
|
||||
which is added by `addDebugTarget:`.
|
||||
|
||||
@param option A new debug option (nil is valid).
|
||||
*/
|
||||
+ (void)setSharedDebugOption:(nullable YYTextDebugOption *)option;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,140 @@
|
|||
//
|
||||
// YYTextDebugOption.m
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/4/8.
|
||||
// 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 "YYTextDebugOption.h"
|
||||
#import "YYTextWeakProxy.h"
|
||||
#import <libkern/OSAtomic.h>
|
||||
#import <pthread.h>
|
||||
|
||||
static pthread_mutex_t _sharedDebugLock;
|
||||
static CFMutableSetRef _sharedDebugTargets = nil;
|
||||
static YYTextDebugOption *_sharedDebugOption = nil;
|
||||
|
||||
static const void* _sharedDebugSetRetain(CFAllocatorRef allocator, const void *value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
static void _sharedDebugSetRelease(CFAllocatorRef allocator, const void *value) {
|
||||
}
|
||||
|
||||
void _sharedDebugSetFunction(const void *value, void *context) {
|
||||
id<YYTextDebugTarget> target = (__bridge id<YYTextDebugTarget>)(value);
|
||||
[target setDebugOption:_sharedDebugOption];
|
||||
}
|
||||
|
||||
static void _initSharedDebug() {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
pthread_mutex_init(&_sharedDebugLock, NULL);
|
||||
CFSetCallBacks callbacks = kCFTypeSetCallBacks;
|
||||
callbacks.retain = _sharedDebugSetRetain;
|
||||
callbacks.release = _sharedDebugSetRelease;
|
||||
_sharedDebugTargets = CFSetCreateMutable(CFAllocatorGetDefault(), 0, &callbacks);
|
||||
});
|
||||
}
|
||||
|
||||
static void _setSharedDebugOption(YYTextDebugOption *option) {
|
||||
_initSharedDebug();
|
||||
pthread_mutex_lock(&_sharedDebugLock);
|
||||
_sharedDebugOption = option.copy;
|
||||
CFSetApplyFunction(_sharedDebugTargets, _sharedDebugSetFunction, NULL);
|
||||
pthread_mutex_unlock(&_sharedDebugLock);
|
||||
}
|
||||
|
||||
static YYTextDebugOption *_getSharedDebugOption() {
|
||||
_initSharedDebug();
|
||||
pthread_mutex_lock(&_sharedDebugLock);
|
||||
YYTextDebugOption *op = _sharedDebugOption;
|
||||
pthread_mutex_unlock(&_sharedDebugLock);
|
||||
return op;
|
||||
}
|
||||
|
||||
static void _addDebugTarget(id<YYTextDebugTarget> target) {
|
||||
_initSharedDebug();
|
||||
pthread_mutex_lock(&_sharedDebugLock);
|
||||
CFSetAddValue(_sharedDebugTargets, (__bridge const void *)(target));
|
||||
pthread_mutex_unlock(&_sharedDebugLock);
|
||||
}
|
||||
|
||||
static void _removeDebugTarget(id<YYTextDebugTarget> target) {
|
||||
_initSharedDebug();
|
||||
pthread_mutex_lock(&_sharedDebugLock);
|
||||
CFSetRemoveValue(_sharedDebugTargets, (__bridge const void *)(target));
|
||||
pthread_mutex_unlock(&_sharedDebugLock);
|
||||
}
|
||||
|
||||
|
||||
@implementation YYTextDebugOption
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
YYTextDebugOption *op = [self.class new];
|
||||
op.baselineColor = self.baselineColor;
|
||||
op.CTFrameBorderColor = self.CTFrameBorderColor;
|
||||
op.CTFrameFillColor = self.CTFrameFillColor;
|
||||
op.CTLineBorderColor = self.CTLineBorderColor;
|
||||
op.CTLineFillColor = self.CTLineFillColor;
|
||||
op.CTLineNumberColor = self.CTLineNumberColor;
|
||||
op.CTRunBorderColor = self.CTRunBorderColor;
|
||||
op.CTRunFillColor = self.CTRunFillColor;
|
||||
op.CTRunNumberColor = self.CTRunNumberColor;
|
||||
op.CGGlyphBorderColor = self.CGGlyphBorderColor;
|
||||
op.CGGlyphFillColor = self.CGGlyphFillColor;
|
||||
return op;
|
||||
}
|
||||
|
||||
- (BOOL)needDrawDebug {
|
||||
if (self.baselineColor ||
|
||||
self.CTFrameBorderColor ||
|
||||
self.CTFrameFillColor ||
|
||||
self.CTLineBorderColor ||
|
||||
self.CTLineFillColor ||
|
||||
self.CTLineNumberColor ||
|
||||
self.CTRunBorderColor ||
|
||||
self.CTRunFillColor ||
|
||||
self.CTRunNumberColor ||
|
||||
self.CGGlyphBorderColor ||
|
||||
self.CGGlyphFillColor) return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)clear {
|
||||
self.baselineColor = nil;
|
||||
self.CTFrameBorderColor = nil;
|
||||
self.CTFrameFillColor = nil;
|
||||
self.CTLineBorderColor = nil;
|
||||
self.CTLineFillColor = nil;
|
||||
self.CTLineNumberColor = nil;
|
||||
self.CTRunBorderColor = nil;
|
||||
self.CTRunFillColor = nil;
|
||||
self.CTRunNumberColor = nil;
|
||||
self.CGGlyphBorderColor = nil;
|
||||
self.CGGlyphFillColor = nil;
|
||||
}
|
||||
|
||||
+ (void)addDebugTarget:(id<YYTextDebugTarget>)target {
|
||||
if (target) _addDebugTarget(target);
|
||||
}
|
||||
|
||||
+ (void)removeDebugTarget:(id<YYTextDebugTarget>)target {
|
||||
if (target) _removeDebugTarget(target);
|
||||
}
|
||||
|
||||
+ (YYTextDebugOption *)sharedDebugOption {
|
||||
return _getSharedDebugOption();
|
||||
}
|
||||
|
||||
+ (void)setSharedDebugOption:(YYTextDebugOption *)option {
|
||||
NSAssert([NSThread isMainThread], @"This method must be called on the main thread");
|
||||
_setSharedDebugOption(option);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// YYTextEffectWindow.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/2/25.
|
||||
// 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(<YYText/YYText.h>)
|
||||
#import <YYText/YYTextMagnifier.h>
|
||||
#import <YYtext/YYTextSelectionView.h>
|
||||
#else
|
||||
#import "YYTextMagnifier.h"
|
||||
#import "YYTextSelectionView.h"
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
A window to display magnifier and extra contents for text view.
|
||||
|
||||
@discussion Use `sharedWindow` to get the instance, don't create your own instance.
|
||||
Typically, you should not use this class directly.
|
||||
*/
|
||||
@interface YYTextEffectWindow : UIWindow
|
||||
|
||||
/// Returns the shared instance (returns nil in App Extension).
|
||||
+ (nullable instancetype)sharedWindow;
|
||||
|
||||
/// Show the magnifier in this window with a 'popup' animation. @param mag A magnifier.
|
||||
- (void)showMagnifier:(YYTextMagnifier *)mag;
|
||||
/// Update the magnifier content and position. @param mag A magnifier.
|
||||
- (void)moveMagnifier:(YYTextMagnifier *)mag;
|
||||
/// Remove the magnifier from this window with a 'shrink' animation. @param mag A magnifier.
|
||||
- (void)hideMagnifier:(YYTextMagnifier *)mag;
|
||||
|
||||
|
||||
/// Show the selection dot in this window if the dot is clipped by the selection view.
|
||||
/// @param selection A selection view.
|
||||
- (void)showSelectionDot:(YYTextSelectionView *)selection;
|
||||
/// Remove the selection dot from this window.
|
||||
/// @param selection A selection view.
|
||||
- (void)hideSelectionDot:(YYTextSelectionView *)selection;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,429 @@
|
|||
//
|
||||
// YYTextEffectWindow.m
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/2/25.
|
||||
// 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 "YYTextEffectWindow.h"
|
||||
#import "YYTextKeyboardManager.h"
|
||||
#import "YYTextUtilities.h"
|
||||
#import "UIView+YYText.h"
|
||||
|
||||
|
||||
@implementation YYTextEffectWindow
|
||||
|
||||
+ (instancetype)sharedWindow {
|
||||
static YYTextEffectWindow *one = nil;
|
||||
if (one == nil) {
|
||||
// iOS 9 compatible
|
||||
NSString *mode = [NSRunLoop currentRunLoop].currentMode;
|
||||
if (mode.length == 27 &&
|
||||
[mode hasPrefix:@"UI"] &&
|
||||
[mode hasSuffix:@"InitializationRunLoopMode"]) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
if (!YYTextIsAppExtension()) {
|
||||
one = [self new];
|
||||
one.frame = (CGRect){.size = YYTextScreenSize()};
|
||||
one.userInteractionEnabled = NO;
|
||||
one.windowLevel = UIWindowLevelStatusBar + 1;
|
||||
one.hidden = NO;
|
||||
|
||||
// for iOS 9:
|
||||
one.opaque = NO;
|
||||
one.backgroundColor = [UIColor clearColor];
|
||||
one.layer.backgroundColor = [UIColor clearColor].CGColor;
|
||||
}
|
||||
});
|
||||
return one;
|
||||
}
|
||||
|
||||
// stop self from becoming the KeyWindow
|
||||
- (void)becomeKeyWindow {
|
||||
[[YYTextSharedApplication().delegate window] makeKeyWindow];
|
||||
}
|
||||
|
||||
- (UIViewController *)rootViewController {
|
||||
for (UIWindow *window in [YYTextSharedApplication() windows]) {
|
||||
if (self == window) continue;
|
||||
if (window.hidden) continue;
|
||||
UIViewController *topViewController = window.rootViewController;
|
||||
if (topViewController) return topViewController;
|
||||
}
|
||||
UIViewController *viewController = [super rootViewController];
|
||||
if (!viewController) {
|
||||
viewController = [UIViewController new];
|
||||
[super setRootViewController:viewController];
|
||||
}
|
||||
return viewController;
|
||||
}
|
||||
|
||||
// Bring self to front
|
||||
- (void)_updateWindowLevel {
|
||||
UIApplication *app = YYTextSharedApplication();
|
||||
if (!app) return;
|
||||
|
||||
UIWindow *top = app.windows.lastObject;
|
||||
UIWindow *key = app.keyWindow;
|
||||
if (key && key.windowLevel > top.windowLevel) top = key;
|
||||
if (top == self) return;
|
||||
self.windowLevel = top.windowLevel + 1;
|
||||
}
|
||||
|
||||
- (YYTextDirection)_keyboardDirection {
|
||||
CGRect keyboardFrame = [YYTextKeyboardManager defaultManager].keyboardFrame;
|
||||
keyboardFrame = [[YYTextKeyboardManager defaultManager] convertRect:keyboardFrame toView:self];
|
||||
if (CGRectIsNull(keyboardFrame) || CGRectIsEmpty(keyboardFrame)) return YYTextDirectionNone;
|
||||
|
||||
if (CGRectGetMinY(keyboardFrame) == 0 &&
|
||||
CGRectGetMinX(keyboardFrame) == 0 &&
|
||||
CGRectGetMaxX(keyboardFrame) == CGRectGetWidth(self.frame))
|
||||
return YYTextDirectionTop;
|
||||
|
||||
if (CGRectGetMaxX(keyboardFrame) == CGRectGetWidth(self.frame) &&
|
||||
CGRectGetMinY(keyboardFrame) == 0 &&
|
||||
CGRectGetMaxY(keyboardFrame) == CGRectGetHeight(self.frame))
|
||||
return YYTextDirectionRight;
|
||||
|
||||
if (CGRectGetMaxY(keyboardFrame) == CGRectGetHeight(self.frame) &&
|
||||
CGRectGetMinX(keyboardFrame) == 0 &&
|
||||
CGRectGetMaxX(keyboardFrame) == CGRectGetWidth(self.frame))
|
||||
return YYTextDirectionBottom;
|
||||
|
||||
if (CGRectGetMinX(keyboardFrame) == 0 &&
|
||||
CGRectGetMinY(keyboardFrame) == 0 &&
|
||||
CGRectGetMaxY(keyboardFrame) == CGRectGetHeight(self.frame))
|
||||
return YYTextDirectionLeft;
|
||||
|
||||
return YYTextDirectionNone;
|
||||
}
|
||||
|
||||
- (CGPoint)_correctedCaptureCenter:(CGPoint)center{
|
||||
CGRect keyboardFrame = [YYTextKeyboardManager defaultManager].keyboardFrame;
|
||||
keyboardFrame = [[YYTextKeyboardManager defaultManager] convertRect:keyboardFrame toView:self];
|
||||
if (!CGRectIsNull(keyboardFrame) && !CGRectIsEmpty(keyboardFrame)) {
|
||||
YYTextDirection direction = [self _keyboardDirection];
|
||||
switch (direction) {
|
||||
case YYTextDirectionTop: {
|
||||
if (center.y < CGRectGetMaxY(keyboardFrame)) center.y = CGRectGetMaxY(keyboardFrame);
|
||||
} break;
|
||||
case YYTextDirectionRight: {
|
||||
if (center.x > CGRectGetMinX(keyboardFrame)) center.x = CGRectGetMinX(keyboardFrame);
|
||||
} break;
|
||||
case YYTextDirectionBottom: {
|
||||
if (center.y > CGRectGetMinY(keyboardFrame)) center.y = CGRectGetMinY(keyboardFrame);
|
||||
} break;
|
||||
case YYTextDirectionLeft: {
|
||||
if (center.x < CGRectGetMaxX(keyboardFrame)) center.x = CGRectGetMaxX(keyboardFrame);
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
return center;
|
||||
}
|
||||
|
||||
- (CGPoint)_correctedCenter:(CGPoint)center forMagnifier:(YYTextMagnifier *)mag rotation:(CGFloat)rotation {
|
||||
CGFloat degree = YYTextRadiansToDegrees(rotation);
|
||||
|
||||
degree /= 45.0;
|
||||
if (degree < 0) degree += (int)(-degree/8.0 + 1) * 8;
|
||||
if (degree > 8) degree -= (int)(degree/8.0) * 8;
|
||||
|
||||
CGFloat caretExt = 10;
|
||||
if (degree <= 1 || degree >= 7) { //top
|
||||
if (mag.type == YYTextMagnifierTypeCaret) {
|
||||
if (center.y < caretExt)
|
||||
center.y = caretExt;
|
||||
} else if (mag.type == YYTextMagnifierTypeRanged) {
|
||||
if (center.y < mag.bounds.size.height)
|
||||
center.y = mag.bounds.size.height;
|
||||
}
|
||||
} else if (1 < degree && degree < 3) { // right
|
||||
if (mag.type == YYTextMagnifierTypeCaret) {
|
||||
if (center.x > self.bounds.size.width - caretExt)
|
||||
center.x = self.bounds.size.width - caretExt;
|
||||
} else if (mag.type == YYTextMagnifierTypeRanged) {
|
||||
if (center.x > self.bounds.size.width - mag.bounds.size.height)
|
||||
center.x = self.bounds.size.width - mag.bounds.size.height;
|
||||
}
|
||||
} else if (3 <= degree && degree <= 5) { // bottom
|
||||
if (mag.type == YYTextMagnifierTypeCaret) {
|
||||
if (center.y > self.bounds.size.height - caretExt)
|
||||
center.y = self.bounds.size.height - caretExt;
|
||||
} else if (mag.type == YYTextMagnifierTypeRanged) {
|
||||
if (center.y > mag.bounds.size.height)
|
||||
center.y = mag.bounds.size.height;
|
||||
}
|
||||
} else if (5 < degree && degree < 7) { // left
|
||||
if (mag.type == YYTextMagnifierTypeCaret) {
|
||||
if (center.x < caretExt)
|
||||
center.x = caretExt;
|
||||
} else if (mag.type == YYTextMagnifierTypeRanged) {
|
||||
if (center.x < mag.bounds.size.height)
|
||||
center.x = mag.bounds.size.height;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CGRect keyboardFrame = [YYTextKeyboardManager defaultManager].keyboardFrame;
|
||||
keyboardFrame = [[YYTextKeyboardManager defaultManager] convertRect:keyboardFrame toView:self];
|
||||
if (!CGRectIsNull(keyboardFrame) && !CGRectIsEmpty(keyboardFrame)) {
|
||||
YYTextDirection direction = [self _keyboardDirection];
|
||||
switch (direction) {
|
||||
case YYTextDirectionTop: {
|
||||
if (mag.type == YYTextMagnifierTypeCaret) {
|
||||
if (center.y - mag.bounds.size.height / 2 < CGRectGetMaxY(keyboardFrame))
|
||||
center.y = CGRectGetMaxY(keyboardFrame) + mag.bounds.size.height / 2;
|
||||
} else if (mag.type == YYTextMagnifierTypeRanged) {
|
||||
if (center.y < CGRectGetMaxY(keyboardFrame)) center.y = CGRectGetMaxY(keyboardFrame);
|
||||
}
|
||||
} break;
|
||||
case YYTextDirectionRight: {
|
||||
if (mag.type == YYTextMagnifierTypeCaret) {
|
||||
if (center.x + mag.bounds.size.height / 2 > CGRectGetMinX(keyboardFrame))
|
||||
center.x = CGRectGetMinX(keyboardFrame) - mag.bounds.size.width / 2;
|
||||
} else if (mag.type == YYTextMagnifierTypeRanged) {
|
||||
if (center.x > CGRectGetMinX(keyboardFrame)) center.x = CGRectGetMinX(keyboardFrame);
|
||||
}
|
||||
} break;
|
||||
case YYTextDirectionBottom: {
|
||||
if (mag.type == YYTextMagnifierTypeCaret) {
|
||||
if (center.y + mag.bounds.size.height / 2 > CGRectGetMinY(keyboardFrame))
|
||||
center.y = CGRectGetMinY(keyboardFrame) - mag.bounds.size.height / 2;
|
||||
} else if (mag.type == YYTextMagnifierTypeRanged) {
|
||||
if (center.y > CGRectGetMinY(keyboardFrame)) center.y = CGRectGetMinY(keyboardFrame);
|
||||
}
|
||||
} break;
|
||||
case YYTextDirectionLeft: {
|
||||
if (mag.type == YYTextMagnifierTypeCaret) {
|
||||
if (center.x - mag.bounds.size.height / 2 < CGRectGetMaxX(keyboardFrame))
|
||||
center.x = CGRectGetMaxX(keyboardFrame) + mag.bounds.size.width / 2;
|
||||
} else if (mag.type == YYTextMagnifierTypeRanged) {
|
||||
if (center.x < CGRectGetMaxX(keyboardFrame)) center.x = CGRectGetMaxX(keyboardFrame);
|
||||
}
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
return center;
|
||||
}
|
||||
|
||||
/**
|
||||
Capture screen snapshot and set it to magnifier.
|
||||
@return Magnifier rotation radius.
|
||||
*/
|
||||
- (CGFloat)_updateMagnifier:(YYTextMagnifier *)mag {
|
||||
UIApplication *app = YYTextSharedApplication();
|
||||
if (!app) return 0;
|
||||
|
||||
UIView *hostView = mag.hostView;
|
||||
UIWindow *hostWindow = [hostView isKindOfClass:[UIWindow class]] ? (id)hostView : hostView.window;
|
||||
if (!hostView || !hostWindow) return 0;
|
||||
CGPoint captureCenter = [self yy_convertPoint:mag.hostCaptureCenter fromViewOrWindow:hostView];
|
||||
captureCenter = [self _correctedCaptureCenter:captureCenter];
|
||||
CGRect captureRect = {.size = mag.snapshotSize};
|
||||
captureRect.origin.x = captureCenter.x - captureRect.size.width / 2;
|
||||
captureRect.origin.y = captureCenter.y - captureRect.size.height / 2;
|
||||
|
||||
CGAffineTransform trans = YYTextCGAffineTransformGetFromViews(hostView, self);
|
||||
CGFloat rotation = YYTextCGAffineTransformGetRotation(trans);
|
||||
|
||||
if (mag.captureDisabled) {
|
||||
if (!mag.snapshot || mag.snapshot.size.width > 1) {
|
||||
static UIImage *placeholder;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
CGRect rect = mag.bounds;
|
||||
rect.origin = CGPointZero;
|
||||
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
[[UIColor colorWithWhite:1 alpha:0.8] set];
|
||||
CGContextFillRect(context, rect);
|
||||
placeholder = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
});
|
||||
mag.captureFadeAnimation = YES;
|
||||
mag.snapshot = placeholder;
|
||||
mag.captureFadeAnimation = NO;
|
||||
}
|
||||
return rotation;
|
||||
}
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(captureRect.size, NO, 0);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
if (!context) return rotation;
|
||||
|
||||
CGPoint tp = CGPointMake(captureRect.size.width / 2, captureRect.size.height / 2);
|
||||
tp = CGPointApplyAffineTransform(tp, CGAffineTransformMakeRotation(rotation));
|
||||
CGContextRotateCTM(context, -rotation);
|
||||
CGContextTranslateCTM(context, tp.x - captureCenter.x, tp.y - captureCenter.y);
|
||||
|
||||
NSMutableArray *windows = app.windows.mutableCopy;
|
||||
UIWindow *keyWindow = app.keyWindow;
|
||||
if (![windows containsObject:keyWindow]) [windows addObject:keyWindow];
|
||||
[windows sortUsingComparator:^NSComparisonResult(UIWindow *w1, UIWindow *w2) {
|
||||
if (w1.windowLevel < w2.windowLevel) return NSOrderedAscending;
|
||||
else if (w1.windowLevel > w2.windowLevel) return NSOrderedDescending;
|
||||
return NSOrderedSame;
|
||||
}];
|
||||
UIScreen *mainScreen = [UIScreen mainScreen];
|
||||
for (UIWindow *window in windows) {
|
||||
if (window.hidden || window.alpha <= 0.01) continue;
|
||||
if (window.screen != mainScreen) continue;
|
||||
if ([window isKindOfClass:self.class]) break; //don't capture window above self
|
||||
CGContextSaveGState(context);
|
||||
CGContextConcatCTM(context, YYTextCGAffineTransformGetFromViews(window, self));
|
||||
[window.layer renderInContext:context]; //render
|
||||
//[window drawViewHierarchyInRect:window.bounds afterScreenUpdates:NO]; //slower when capture whole window
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
if (mag.snapshot.size.width == 1) {
|
||||
mag.captureFadeAnimation = YES;
|
||||
}
|
||||
mag.snapshot = image;
|
||||
mag.captureFadeAnimation = NO;
|
||||
return rotation;
|
||||
}
|
||||
|
||||
- (void)showMagnifier:(YYTextMagnifier *)mag {
|
||||
if (!mag) return;
|
||||
if (mag.superview != self) [self addSubview:mag];
|
||||
[self _updateWindowLevel];
|
||||
CGFloat rotation = [self _updateMagnifier:mag];
|
||||
CGPoint center = [self yy_convertPoint:mag.hostPopoverCenter fromViewOrWindow:mag.hostView];
|
||||
CGAffineTransform trans = CGAffineTransformMakeRotation(rotation);
|
||||
trans = CGAffineTransformScale(trans, 0.3, 0.3);
|
||||
mag.transform = trans;
|
||||
mag.center = center;
|
||||
if (mag.type == YYTextMagnifierTypeRanged) {
|
||||
mag.alpha = 0;
|
||||
}
|
||||
NSTimeInterval time = mag.type == YYTextMagnifierTypeCaret ? 0.08 : 0.1;
|
||||
[UIView animateWithDuration:time delay:0 options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState animations:^{
|
||||
if (mag.type == YYTextMagnifierTypeCaret) {
|
||||
CGPoint newCenter = CGPointMake(0, -mag.fitSize.height / 2);
|
||||
newCenter = CGPointApplyAffineTransform(newCenter, CGAffineTransformMakeRotation(rotation));
|
||||
newCenter.x += center.x;
|
||||
newCenter.y += center.y;
|
||||
mag.center = [self _correctedCenter:newCenter forMagnifier:mag rotation:rotation];
|
||||
} else {
|
||||
mag.center = [self _correctedCenter:center forMagnifier:mag rotation:rotation];
|
||||
}
|
||||
mag.transform = CGAffineTransformMakeRotation(rotation);
|
||||
mag.alpha = 1;
|
||||
} completion:^(BOOL finished) {
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)moveMagnifier:(YYTextMagnifier *)mag {
|
||||
if (!mag) return;
|
||||
[self _updateWindowLevel];
|
||||
CGFloat rotation = [self _updateMagnifier:mag];
|
||||
CGPoint center = [self yy_convertPoint:mag.hostPopoverCenter fromViewOrWindow:mag.hostView];
|
||||
if (mag.type == YYTextMagnifierTypeCaret) {
|
||||
CGPoint newCenter = CGPointMake(0, -mag.fitSize.height / 2);
|
||||
newCenter = CGPointApplyAffineTransform(newCenter, CGAffineTransformMakeRotation(rotation));
|
||||
newCenter.x += center.x;
|
||||
newCenter.y += center.y;
|
||||
mag.center = [self _correctedCenter:newCenter forMagnifier:mag rotation:rotation];
|
||||
} else {
|
||||
mag.center = [self _correctedCenter:center forMagnifier:mag rotation:rotation];
|
||||
}
|
||||
mag.transform = CGAffineTransformMakeRotation(rotation);
|
||||
}
|
||||
|
||||
- (void)hideMagnifier:(YYTextMagnifier *)mag {
|
||||
if (!mag) return;
|
||||
if (mag.superview != self) return;
|
||||
CGFloat rotation = [self _updateMagnifier:mag];
|
||||
CGPoint center = [self yy_convertPoint:mag.hostPopoverCenter fromViewOrWindow:mag.hostView];
|
||||
NSTimeInterval time = mag.type == YYTextMagnifierTypeCaret ? 0.20 : 0.15;
|
||||
[UIView animateWithDuration:time delay:0 options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState animations:^{
|
||||
|
||||
CGAffineTransform trans = CGAffineTransformMakeRotation(rotation);
|
||||
trans = CGAffineTransformScale(trans, 0.01, 0.01);
|
||||
mag.transform = trans;
|
||||
|
||||
if (mag.type == YYTextMagnifierTypeCaret) {
|
||||
CGPoint newCenter = CGPointMake(0, -mag.fitSize.height / 2);
|
||||
newCenter = CGPointApplyAffineTransform(newCenter, CGAffineTransformMakeRotation(rotation));
|
||||
newCenter.x += center.x;
|
||||
newCenter.y += center.y;
|
||||
mag.center = [self _correctedCenter:newCenter forMagnifier:mag rotation:rotation];
|
||||
} else {
|
||||
mag.center = [self _correctedCenter:center forMagnifier:mag rotation:rotation];
|
||||
mag.alpha = 0;
|
||||
}
|
||||
|
||||
} completion:^(BOOL finished) {
|
||||
if (finished) {
|
||||
[mag removeFromSuperview];
|
||||
mag.transform = CGAffineTransformIdentity;
|
||||
mag.alpha = 1;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_updateSelectionGrabberDot:(YYSelectionGrabberDot *)dot selection:(YYTextSelectionView *)selection{
|
||||
dot.mirror.hidden = YES;
|
||||
if (selection.hostView.clipsToBounds == YES && dot.yy_visibleAlpha > 0.1) {
|
||||
CGRect dotRect = [dot yy_convertRect:dot.bounds toViewOrWindow:self];
|
||||
BOOL dotInKeyboard = NO;
|
||||
|
||||
CGRect keyboardFrame = [YYTextKeyboardManager defaultManager].keyboardFrame;
|
||||
keyboardFrame = [[YYTextKeyboardManager defaultManager] convertRect:keyboardFrame toView:self];
|
||||
if (!CGRectIsNull(keyboardFrame) && !CGRectIsEmpty(keyboardFrame)) {
|
||||
CGRect inter = CGRectIntersection(dotRect, keyboardFrame);
|
||||
if (!CGRectIsNull(inter) && (inter.size.width > 1 || inter.size.height > 1)) {
|
||||
dotInKeyboard = YES;
|
||||
}
|
||||
}
|
||||
if (!dotInKeyboard) {
|
||||
CGRect hostRect = [selection.hostView convertRect:selection.hostView.bounds toView:self];
|
||||
CGRect intersection = CGRectIntersection(dotRect, hostRect);
|
||||
if (YYTextCGRectGetArea(intersection) < YYTextCGRectGetArea(dotRect)) {
|
||||
CGFloat dist = YYTextCGPointGetDistanceToRect(YYTextCGRectGetCenter(dotRect), hostRect);
|
||||
if (dist < CGRectGetWidth(dot.frame) * 0.55) {
|
||||
dot.mirror.hidden = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CGPoint center = [dot yy_convertPoint:CGPointMake(CGRectGetWidth(dot.frame) / 2, CGRectGetHeight(dot.frame) / 2) toViewOrWindow:self];
|
||||
if (isnan(center.x) || isnan(center.y) || isinf(center.x) || isinf(center.y)) {
|
||||
dot.mirror.hidden = YES;
|
||||
} else {
|
||||
dot.mirror.center = center;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)showSelectionDot:(YYTextSelectionView *)selection {
|
||||
if (!selection) return;
|
||||
[self _updateWindowLevel];
|
||||
[self insertSubview:selection.startGrabber.dot.mirror atIndex:0];
|
||||
[self insertSubview:selection.endGrabber.dot.mirror atIndex:0];
|
||||
[self _updateSelectionGrabberDot:selection.startGrabber.dot selection:selection];
|
||||
[self _updateSelectionGrabberDot:selection.endGrabber.dot selection:selection];
|
||||
}
|
||||
|
||||
- (void)hideSelectionDot:(YYTextSelectionView *)selection {
|
||||
if (!selection) return;
|
||||
[selection.startGrabber.dot.mirror removeFromSuperview];
|
||||
[selection.endGrabber.dot.mirror removeFromSuperview];
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,87 @@
|
|||
//
|
||||
// YYTextInput.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/4/17.
|
||||
// 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
|
||||
|
||||
/**
|
||||
Text position affinity. For example, the offset appears after the last
|
||||
character on a line is backward affinity, before the first character on
|
||||
the following line is forward affinity.
|
||||
*/
|
||||
typedef NS_ENUM(NSInteger, YYTextAffinity) {
|
||||
YYTextAffinityForward = 0, ///< offset appears before the character
|
||||
YYTextAffinityBackward = 1, ///< offset appears after the character
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
A YYTextPosition object represents a position in a text container; in other words,
|
||||
it is an index into the backing string in a text-displaying view.
|
||||
|
||||
YYTextPosition has the same API as Apple's implementation in UITextView/UITextField,
|
||||
so you can alse use it to interact with UITextView/UITextField.
|
||||
*/
|
||||
@interface YYTextPosition : UITextPosition <NSCopying>
|
||||
|
||||
@property (nonatomic, readonly) NSInteger offset;
|
||||
@property (nonatomic, readonly) YYTextAffinity affinity;
|
||||
|
||||
+ (instancetype)positionWithOffset:(NSInteger)offset;
|
||||
+ (instancetype)positionWithOffset:(NSInteger)offset affinity:(YYTextAffinity) affinity;
|
||||
|
||||
- (NSComparisonResult)compare:(id)otherPosition;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
A YYTextRange object represents a range of characters in a text container; in other words,
|
||||
it identifies a starting index and an ending index in string backing a text-displaying view.
|
||||
|
||||
YYTextRange has the same API as Apple's implementation in UITextView/UITextField,
|
||||
so you can alse use it to interact with UITextView/UITextField.
|
||||
*/
|
||||
@interface YYTextRange : UITextRange <NSCopying>
|
||||
|
||||
@property (nonatomic, readonly) YYTextPosition *start;
|
||||
@property (nonatomic, readonly) YYTextPosition *end;
|
||||
@property (nonatomic, readonly, getter=isEmpty) BOOL empty;
|
||||
|
||||
+ (instancetype)rangeWithRange:(NSRange)range;
|
||||
+ (instancetype)rangeWithRange:(NSRange)range affinity:(YYTextAffinity) affinity;
|
||||
+ (instancetype)rangeWithStart:(YYTextPosition *)start end:(YYTextPosition *)end;
|
||||
+ (instancetype)defaultRange; ///< <{0,0} Forward>
|
||||
|
||||
- (NSRange)asRange;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
A YYTextSelectionRect object encapsulates information about a selected range of
|
||||
text in a text-displaying view.
|
||||
|
||||
YYTextSelectionRect has the same API as Apple's implementation in UITextView/UITextField,
|
||||
so you can alse use it to interact with UITextView/UITextField.
|
||||
*/
|
||||
@interface YYTextSelectionRect : UITextSelectionRect <NSCopying>
|
||||
|
||||
@property (nonatomic, readwrite) CGRect rect;
|
||||
@property (nonatomic, readwrite) UITextWritingDirection writingDirection;
|
||||
@property (nonatomic, readwrite) BOOL containsStart;
|
||||
@property (nonatomic, readwrite) BOOL containsEnd;
|
||||
@property (nonatomic, readwrite) BOOL isVertical;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,152 @@
|
|||
//
|
||||
// YYTextInput.m
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/4/17.
|
||||
// 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 "YYTextInput.h"
|
||||
#import "YYTextUtilities.h"
|
||||
|
||||
|
||||
@implementation YYTextPosition
|
||||
|
||||
+ (instancetype)positionWithOffset:(NSInteger)offset {
|
||||
return [self positionWithOffset:offset affinity:YYTextAffinityForward];
|
||||
}
|
||||
|
||||
+ (instancetype)positionWithOffset:(NSInteger)offset affinity:(YYTextAffinity)affinity {
|
||||
YYTextPosition *p = [self new];
|
||||
p->_offset = offset;
|
||||
p->_affinity = affinity;
|
||||
return p;
|
||||
}
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
return [self.class positionWithOffset:_offset affinity:_affinity];
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<%@: %p> (%@%@)", self.class, self, @(_offset), _affinity == YYTextAffinityForward ? @"F":@"B"];
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
return _offset * 2 + (_affinity == YYTextAffinityForward ? 1 : 0);
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(YYTextPosition *)object {
|
||||
if (!object) return NO;
|
||||
return _offset == object.offset && _affinity == object.affinity;
|
||||
}
|
||||
|
||||
- (NSComparisonResult)compare:(YYTextPosition *)otherPosition {
|
||||
if (!otherPosition) return NSOrderedAscending;
|
||||
if (_offset < otherPosition.offset) return NSOrderedAscending;
|
||||
if (_offset > otherPosition.offset) return NSOrderedDescending;
|
||||
if (_affinity == YYTextAffinityBackward && otherPosition.affinity == YYTextAffinityForward) return NSOrderedAscending;
|
||||
if (_affinity == YYTextAffinityForward && otherPosition.affinity == YYTextAffinityBackward) return NSOrderedDescending;
|
||||
return NSOrderedSame;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation YYTextRange {
|
||||
YYTextPosition *_start;
|
||||
YYTextPosition *_end;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (!self) return nil;
|
||||
_start = [YYTextPosition positionWithOffset:0];
|
||||
_end = [YYTextPosition positionWithOffset:0];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (YYTextPosition *)start {
|
||||
return _start;
|
||||
}
|
||||
|
||||
- (YYTextPosition *)end {
|
||||
return _end;
|
||||
}
|
||||
|
||||
- (BOOL)isEmpty {
|
||||
return _start.offset == _end.offset;
|
||||
}
|
||||
|
||||
- (NSRange)asRange {
|
||||
return NSMakeRange(_start.offset, _end.offset - _start.offset);
|
||||
}
|
||||
|
||||
+ (instancetype)rangeWithRange:(NSRange)range {
|
||||
return [self rangeWithRange:range affinity:YYTextAffinityForward];
|
||||
}
|
||||
|
||||
+ (instancetype)rangeWithRange:(NSRange)range affinity:(YYTextAffinity)affinity {
|
||||
YYTextPosition *start = [YYTextPosition positionWithOffset:range.location affinity:affinity];
|
||||
YYTextPosition *end = [YYTextPosition positionWithOffset:range.location + range.length affinity:affinity];
|
||||
return [self rangeWithStart:start end:end];
|
||||
}
|
||||
|
||||
+ (instancetype)rangeWithStart:(YYTextPosition *)start end:(YYTextPosition *)end {
|
||||
if (!start || !end) return nil;
|
||||
if ([start compare:end] == NSOrderedDescending) {
|
||||
YYTEXT_SWAP(start, end);
|
||||
}
|
||||
YYTextRange *range = [YYTextRange new];
|
||||
range->_start = start;
|
||||
range->_end = end;
|
||||
return range;
|
||||
}
|
||||
|
||||
+ (instancetype)defaultRange {
|
||||
return [self new];
|
||||
}
|
||||
|
||||
- (instancetype)copyWithZone:(NSZone *)zone {
|
||||
return [self.class rangeWithStart:_start end:_end];
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<%@: %p> (%@, %@)%@", self.class, self, @(_start.offset), @(_end.offset - _start.offset), _end.affinity == YYTextAffinityForward ? @"F":@"B"];
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
return (sizeof(NSUInteger) == 8 ? OSSwapInt64(_start.hash) : OSSwapInt32(_start.hash)) + _end.hash;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(YYTextRange *)object {
|
||||
if (!object) return NO;
|
||||
return [_start isEqual:object.start] && [_end isEqual:object.end];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation YYTextSelectionRect
|
||||
|
||||
@synthesize rect = _rect;
|
||||
@synthesize writingDirection = _writingDirection;
|
||||
@synthesize containsStart = _containsStart;
|
||||
@synthesize containsEnd = _containsEnd;
|
||||
@synthesize isVertical = _isVertical;
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
YYTextSelectionRect *one = [self.class new];
|
||||
one.rect = _rect;
|
||||
one.writingDirection = _writingDirection;
|
||||
one.containsStart = _containsStart;
|
||||
one.containsEnd = _containsEnd;
|
||||
one.isVertical = _isVertical;
|
||||
return one;
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,98 @@
|
|||
//
|
||||
// YYTextKeyboardManager.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/6/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>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
System keyboard transition information.
|
||||
Use -[YYTextKeyboardManager convertRect:toView:] to convert frame to specified view.
|
||||
*/
|
||||
typedef struct {
|
||||
BOOL fromVisible; ///< Keyboard visible before transition.
|
||||
BOOL toVisible; ///< Keyboard visible after transition.
|
||||
CGRect fromFrame; ///< Keyboard frame before transition.
|
||||
CGRect toFrame; ///< Keyboard frame after transition.
|
||||
NSTimeInterval animationDuration; ///< Keyboard transition animation duration.
|
||||
UIViewAnimationCurve animationCurve; ///< Keyboard transition animation curve.
|
||||
UIViewAnimationOptions animationOption; ///< Keybaord transition animation option.
|
||||
} YYTextKeyboardTransition;
|
||||
|
||||
|
||||
/**
|
||||
The YYTextKeyboardObserver protocol defines the method you can use
|
||||
to receive system keyboard change information.
|
||||
*/
|
||||
@protocol YYTextKeyboardObserver <NSObject>
|
||||
@optional
|
||||
- (void)keyboardChangedWithTransition:(YYTextKeyboardTransition)transition;
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
A YYTextKeyboardManager object lets you get the system keyboard information,
|
||||
and track the keyboard visible/frame/transition.
|
||||
|
||||
@discussion You should access this class in main thread.
|
||||
Compatible: iPhone/iPad with iOS6/7/8/9.
|
||||
*/
|
||||
@interface YYTextKeyboardManager : NSObject
|
||||
|
||||
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
|
||||
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;
|
||||
|
||||
/// Get the default manager (returns nil in App Extension).
|
||||
+ (nullable instancetype)defaultManager;
|
||||
|
||||
/// Get the keyboard window. nil if there's no keyboard window.
|
||||
@property (nullable, nonatomic, readonly) UIWindow *keyboardWindow;
|
||||
|
||||
/// Get the keyboard view. nil if there's no keyboard view.
|
||||
@property (nullable, nonatomic, readonly) UIView *keyboardView;
|
||||
|
||||
/// Whether the keyboard is visible.
|
||||
@property (nonatomic, readonly, getter=isKeyboardVisible) BOOL keyboardVisible;
|
||||
|
||||
/// Get the keyboard frame. CGRectNull if there's no keyboard view.
|
||||
/// Use convertRect:toView: to convert frame to specified view.
|
||||
@property (nonatomic, readonly) CGRect keyboardFrame;
|
||||
|
||||
|
||||
/**
|
||||
Add an observer to manager to get keyboard change information.
|
||||
This method makes a weak reference to the observer.
|
||||
|
||||
@param observer An observer.
|
||||
This method will do nothing if the observer is nil, or already added.
|
||||
*/
|
||||
- (void)addObserver:(id<YYTextKeyboardObserver>)observer;
|
||||
|
||||
/**
|
||||
Remove an observer from manager.
|
||||
|
||||
@param observer An observer.
|
||||
This method will do nothing if the observer is nil, or not in manager.
|
||||
*/
|
||||
- (void)removeObserver:(id<YYTextKeyboardObserver>)observer;
|
||||
|
||||
/**
|
||||
Convert rect to specified view or window.
|
||||
|
||||
@param rect The frame rect.
|
||||
@param view A specified view or window (pass nil to convert for main window).
|
||||
@return The converted rect in specifeid view.
|
||||
*/
|
||||
- (CGRect)convertRect:(CGRect)rect toView:(nullable UIView *)view;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,521 @@
|
|||
//
|
||||
// YYTextKeyboardManager.m
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/6/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 "YYTextKeyboardManager.h"
|
||||
#import "YYTextUtilities.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
|
||||
static int _YYTextKeyboardViewFrameObserverKey;
|
||||
|
||||
/// Observer for view's frame/bounds/center/transform
|
||||
@interface _YYTextKeyboardViewFrameObserver : NSObject
|
||||
@property (nonatomic, copy) void (^notifyBlock)(UIView *keyboard);
|
||||
- (void)addToKeyboardView:(UIView *)keyboardView;
|
||||
+ (instancetype)observerForView:(UIView *)keyboardView;
|
||||
@end
|
||||
|
||||
|
||||
@implementation _YYTextKeyboardViewFrameObserver {
|
||||
__unsafe_unretained UIView *_keyboardView;
|
||||
}
|
||||
- (void)addToKeyboardView:(UIView *)keyboardView {
|
||||
if (_keyboardView == keyboardView) return;
|
||||
if (_keyboardView) {
|
||||
[self removeFrameObserver];
|
||||
objc_setAssociatedObject(_keyboardView, &_YYTextKeyboardViewFrameObserverKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
_keyboardView = keyboardView;
|
||||
if (keyboardView) {
|
||||
[self addFrameObserver];
|
||||
}
|
||||
objc_setAssociatedObject(keyboardView, &_YYTextKeyboardViewFrameObserverKey, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
}
|
||||
|
||||
- (void)removeFrameObserver {
|
||||
[_keyboardView removeObserver:self forKeyPath:@"frame"];
|
||||
[_keyboardView removeObserver:self forKeyPath:@"center"];
|
||||
[_keyboardView removeObserver:self forKeyPath:@"bounds"];
|
||||
[_keyboardView removeObserver:self forKeyPath:@"transform"];
|
||||
_keyboardView = nil;
|
||||
}
|
||||
|
||||
- (void)addFrameObserver {
|
||||
if (!_keyboardView) return;
|
||||
[_keyboardView addObserver:self forKeyPath:@"frame" options:kNilOptions context:NULL];
|
||||
[_keyboardView addObserver:self forKeyPath:@"center" options:kNilOptions context:NULL];
|
||||
[_keyboardView addObserver:self forKeyPath:@"bounds" options:kNilOptions context:NULL];
|
||||
[_keyboardView addObserver:self forKeyPath:@"transform" options:kNilOptions context:NULL];
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self removeFrameObserver];
|
||||
}
|
||||
|
||||
+ (instancetype)observerForView:(UIView *)keyboardView {
|
||||
if (!keyboardView) return nil;
|
||||
return objc_getAssociatedObject(keyboardView, &_YYTextKeyboardViewFrameObserverKey);
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
|
||||
|
||||
BOOL isPrior = [[change objectForKey:NSKeyValueChangeNotificationIsPriorKey] boolValue];
|
||||
if (isPrior) return;
|
||||
|
||||
NSKeyValueChange changeKind = [[change objectForKey:NSKeyValueChangeKindKey] integerValue];
|
||||
if (changeKind != NSKeyValueChangeSetting) return;
|
||||
|
||||
id newVal = [change objectForKey:NSKeyValueChangeNewKey];
|
||||
if (newVal == [NSNull null]) newVal = nil;
|
||||
|
||||
if (_notifyBlock) {
|
||||
_notifyBlock(_keyboardView);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation YYTextKeyboardManager {
|
||||
NSHashTable *_observers;
|
||||
|
||||
CGRect _fromFrame;
|
||||
BOOL _fromVisible;
|
||||
UIInterfaceOrientation _fromOrientation;
|
||||
|
||||
CGRect _notificationFromFrame;
|
||||
CGRect _notificationToFrame;
|
||||
NSTimeInterval _notificationDuration;
|
||||
UIViewAnimationCurve _notificationCurve;
|
||||
BOOL _hasNotification;
|
||||
|
||||
CGRect _observedToFrame;
|
||||
BOOL _hasObservedChange;
|
||||
|
||||
BOOL _lastIsNotification;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
@throw [NSException exceptionWithName:@"YYTextKeyboardManager init error" reason:@"Use 'defaultManager' to get instance." userInfo:nil];
|
||||
return [super init];
|
||||
}
|
||||
|
||||
- (instancetype)_init {
|
||||
self = [super init];
|
||||
_observers = [[NSHashTable alloc] initWithOptions:NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(_keyboardFrameWillChangeNotification:)
|
||||
name:UIKeyboardWillChangeFrameNotification
|
||||
object:nil];
|
||||
// for iPad (iOS 9)
|
||||
if ([UIDevice currentDevice].systemVersion.floatValue >= 9) {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(_keyboardFrameDidChangeNotification:)
|
||||
name:UIKeyboardDidChangeFrameNotification
|
||||
object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_initFrameObserver {
|
||||
UIView *keyboardView = self.keyboardView;
|
||||
if (!keyboardView) return;
|
||||
__weak typeof(self) _self = self;
|
||||
_YYTextKeyboardViewFrameObserver *observer = [_YYTextKeyboardViewFrameObserver observerForView:keyboardView];
|
||||
if (!observer) {
|
||||
observer = [_YYTextKeyboardViewFrameObserver new];
|
||||
observer.notifyBlock = ^(UIView *keyboard) {
|
||||
[_self _keyboardFrameChanged:keyboard];
|
||||
};
|
||||
[observer addToKeyboardView:keyboardView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
+ (instancetype)defaultManager {
|
||||
static YYTextKeyboardManager *mgr = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
if (!YYTextIsAppExtension()) {
|
||||
mgr = [[self alloc] _init];
|
||||
}
|
||||
});
|
||||
return mgr;
|
||||
}
|
||||
|
||||
+ (void)load {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self defaultManager];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)addObserver:(id<YYTextKeyboardObserver>)observer {
|
||||
if (!observer) return;
|
||||
[_observers addObject:observer];
|
||||
}
|
||||
|
||||
- (void)removeObserver:(id<YYTextKeyboardObserver>)observer {
|
||||
if (!observer) return;
|
||||
[_observers removeObject:observer];
|
||||
}
|
||||
|
||||
- (UIWindow *)keyboardWindow {
|
||||
UIApplication *app = YYTextSharedApplication();
|
||||
if (!app) return nil;
|
||||
|
||||
UIWindow *window = nil;
|
||||
for (window in app.windows) {
|
||||
if ([self _getKeyboardViewFromWindow:window]) return window;
|
||||
}
|
||||
window = app.keyWindow;
|
||||
if ([self _getKeyboardViewFromWindow:window]) return window;
|
||||
|
||||
NSMutableArray *kbWindows = nil;
|
||||
for (window in app.windows) {
|
||||
NSString *windowName = NSStringFromClass(window.class);
|
||||
if ([self _systemVersion] < 9) {
|
||||
// UITextEffectsWindow
|
||||
if (windowName.length == 19 &&
|
||||
[windowName hasPrefix:@"UI"] &&
|
||||
[windowName hasSuffix:@"TextEffectsWindow"]) {
|
||||
if (!kbWindows) kbWindows = [NSMutableArray new];
|
||||
[kbWindows addObject:window];
|
||||
}
|
||||
} else {
|
||||
// UIRemoteKeyboardWindow
|
||||
if (windowName.length == 22 &&
|
||||
[windowName hasPrefix:@"UI"] &&
|
||||
[windowName hasSuffix:@"RemoteKeyboardWindow"]) {
|
||||
if (!kbWindows) kbWindows = [NSMutableArray new];
|
||||
[kbWindows addObject:window];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (kbWindows.count == 1) {
|
||||
return kbWindows.firstObject;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (UIView *)keyboardView {
|
||||
UIApplication *app = YYTextSharedApplication();
|
||||
if (!app) return nil;
|
||||
|
||||
UIWindow *window = nil;
|
||||
UIView *view = nil;
|
||||
for (window in app.windows) {
|
||||
view = [self _getKeyboardViewFromWindow:window];
|
||||
if (view) return view;
|
||||
}
|
||||
window = app.keyWindow;
|
||||
view = [self _getKeyboardViewFromWindow:window];
|
||||
if (view) return view;
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)isKeyboardVisible {
|
||||
UIWindow *window = self.keyboardWindow;
|
||||
if (!window) return NO;
|
||||
UIView *view = self.keyboardView;
|
||||
if (!view) return NO;
|
||||
CGRect rect = CGRectIntersection(window.bounds, view.frame);
|
||||
if (CGRectIsNull(rect)) return NO;
|
||||
if (CGRectIsInfinite(rect)) return NO;
|
||||
return rect.size.width > 0 && rect.size.height > 0;
|
||||
}
|
||||
|
||||
- (CGRect)keyboardFrame {
|
||||
UIView *keyboard = [self keyboardView];
|
||||
if (!keyboard) return CGRectNull;
|
||||
|
||||
CGRect frame = CGRectNull;
|
||||
UIWindow *window = keyboard.window;
|
||||
if (window) {
|
||||
frame = [window convertRect:keyboard.frame toWindow:nil];
|
||||
} else {
|
||||
frame = keyboard.frame;
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
#pragma mark - private
|
||||
|
||||
- (double)_systemVersion {
|
||||
static double v;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
v = [UIDevice currentDevice].systemVersion.doubleValue;
|
||||
});
|
||||
return v;
|
||||
}
|
||||
|
||||
- (UIView *)_getKeyboardViewFromWindow:(UIWindow *)window {
|
||||
/*
|
||||
iOS 6/7:
|
||||
UITextEffectsWindow
|
||||
UIPeripheralHostView << keyboard
|
||||
|
||||
iOS 8:
|
||||
UITextEffectsWindow
|
||||
UIInputSetContainerView
|
||||
UIInputSetHostView << keyboard
|
||||
|
||||
iOS 9:
|
||||
UIRemoteKeyboardWindow
|
||||
UIInputSetContainerView
|
||||
UIInputSetHostView << keyboard
|
||||
*/
|
||||
if (!window) return nil;
|
||||
|
||||
// Get the window
|
||||
NSString *windowName = NSStringFromClass(window.class);
|
||||
if ([self _systemVersion] < 9) {
|
||||
// UITextEffectsWindow
|
||||
if (windowName.length != 19) return nil;
|
||||
if (![windowName hasPrefix:@"UI"]) return nil;
|
||||
if (![windowName hasSuffix:@"TextEffectsWindow"]) return nil;
|
||||
} else {
|
||||
// UIRemoteKeyboardWindow
|
||||
if (windowName.length != 22) return nil;
|
||||
if (![windowName hasPrefix:@"UI"]) return nil;
|
||||
if (![windowName hasSuffix:@"RemoteKeyboardWindow"]) return nil;
|
||||
}
|
||||
|
||||
// Get the view
|
||||
if ([self _systemVersion] < 8) {
|
||||
// UIPeripheralHostView
|
||||
for (UIView *view in window.subviews) {
|
||||
NSString *viewName = NSStringFromClass(view.class);
|
||||
if (viewName.length != 20) continue;
|
||||
if (![viewName hasPrefix:@"UI"]) continue;
|
||||
if (![viewName hasSuffix:@"PeripheralHostView"]) continue;
|
||||
return view;
|
||||
}
|
||||
} else {
|
||||
// UIInputSetContainerView
|
||||
for (UIView *view in window.subviews) {
|
||||
NSString *viewName = NSStringFromClass(view.class);
|
||||
if (viewName.length != 23) continue;
|
||||
if (![viewName hasPrefix:@"UI"]) continue;
|
||||
if (![viewName hasSuffix:@"InputSetContainerView"]) continue;
|
||||
// UIInputSetHostView
|
||||
for (UIView *subView in view.subviews) {
|
||||
NSString *subViewName = NSStringFromClass(subView.class);
|
||||
if (subViewName.length != 18) continue;
|
||||
if (![subViewName hasPrefix:@"UI"]) continue;
|
||||
if (![subViewName hasSuffix:@"InputSetHostView"]) continue;
|
||||
return subView;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)_keyboardFrameWillChangeNotification:(NSNotification *)notif {
|
||||
if (![notif.name isEqualToString:UIKeyboardWillChangeFrameNotification]) return;
|
||||
NSDictionary *info = notif.userInfo;
|
||||
if (!info) return;
|
||||
|
||||
[self _initFrameObserver];
|
||||
|
||||
NSValue *beforeValue = info[UIKeyboardFrameBeginUserInfoKey];
|
||||
NSValue *afterValue = info[UIKeyboardFrameEndUserInfoKey];
|
||||
NSNumber *curveNumber = info[UIKeyboardAnimationCurveUserInfoKey];
|
||||
NSNumber *durationNumber = info[UIKeyboardAnimationDurationUserInfoKey];
|
||||
|
||||
CGRect before = beforeValue.CGRectValue;
|
||||
CGRect after = afterValue.CGRectValue;
|
||||
UIViewAnimationCurve curve = curveNumber.integerValue;
|
||||
NSTimeInterval duration = durationNumber.doubleValue;
|
||||
|
||||
// ignore zero end frame
|
||||
if (after.size.width <= 0 && after.size.height <= 0) return;
|
||||
|
||||
_notificationFromFrame = before;
|
||||
_notificationToFrame = after;
|
||||
_notificationCurve = curve;
|
||||
_notificationDuration = duration;
|
||||
_hasNotification = YES;
|
||||
_lastIsNotification = YES;
|
||||
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_notifyAllObservers) object:nil];
|
||||
if (duration == 0) {
|
||||
[self performSelector:@selector(_notifyAllObservers) withObject:nil afterDelay:0 inModes:@[NSRunLoopCommonModes]];
|
||||
} else {
|
||||
[self _notifyAllObservers];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_keyboardFrameDidChangeNotification:(NSNotification *)notif {
|
||||
if (![notif.name isEqualToString:UIKeyboardDidChangeFrameNotification]) return;
|
||||
NSDictionary *info = notif.userInfo;
|
||||
if (!info) return;
|
||||
|
||||
[self _initFrameObserver];
|
||||
|
||||
NSValue *afterValue = info[UIKeyboardFrameEndUserInfoKey];
|
||||
CGRect after = afterValue.CGRectValue;
|
||||
|
||||
// ignore zero end frame
|
||||
if (after.size.width <= 0 && after.size.height <= 0) return;
|
||||
|
||||
_notificationToFrame = after;
|
||||
_notificationCurve = UIViewAnimationCurveEaseInOut;
|
||||
_notificationDuration = 0;
|
||||
_hasNotification = YES;
|
||||
_lastIsNotification = YES;
|
||||
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_notifyAllObservers) object:nil];
|
||||
[self performSelector:@selector(_notifyAllObservers) withObject:nil afterDelay:0 inModes:@[NSRunLoopCommonModes]];
|
||||
}
|
||||
|
||||
- (void)_keyboardFrameChanged:(UIView *)keyboard {
|
||||
if (keyboard != self.keyboardView) return;
|
||||
|
||||
UIWindow *window = keyboard.window;
|
||||
if (window) {
|
||||
_observedToFrame = [window convertRect:keyboard.frame toWindow:nil];
|
||||
} else {
|
||||
_observedToFrame = keyboard.frame;
|
||||
}
|
||||
_hasObservedChange = YES;
|
||||
_lastIsNotification = NO;
|
||||
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_notifyAllObservers) object:nil];
|
||||
[self performSelector:@selector(_notifyAllObservers) withObject:nil afterDelay:0 inModes:@[NSRunLoopCommonModes]];
|
||||
}
|
||||
|
||||
- (void)_notifyAllObservers {
|
||||
UIApplication *app = YYTextSharedApplication();
|
||||
if (!app) return;
|
||||
|
||||
UIView *keyboard = self.keyboardView;
|
||||
UIWindow *window = keyboard.window;
|
||||
if (!window) {
|
||||
window = app.keyWindow;
|
||||
}
|
||||
if (!window) {
|
||||
window = app.windows.firstObject;
|
||||
}
|
||||
|
||||
YYTextKeyboardTransition trans = {0};
|
||||
|
||||
// from
|
||||
if (_fromFrame.size.width == 0 && _fromFrame.size.height == 0) { // first notify
|
||||
_fromFrame.size.width = window.bounds.size.width;
|
||||
_fromFrame.size.height = trans.toFrame.size.height;
|
||||
_fromFrame.origin.x = trans.toFrame.origin.x;
|
||||
_fromFrame.origin.y = window.bounds.size.height;
|
||||
}
|
||||
trans.fromFrame = _fromFrame;
|
||||
trans.fromVisible = _fromVisible;
|
||||
|
||||
// to
|
||||
if (_lastIsNotification || (_hasObservedChange && CGRectEqualToRect(_observedToFrame, _notificationToFrame))) {
|
||||
trans.toFrame = _notificationToFrame;
|
||||
trans.animationDuration = _notificationDuration;
|
||||
trans.animationCurve = _notificationCurve;
|
||||
trans.animationOption = _notificationCurve << 16;
|
||||
|
||||
// Fix iPad(iOS7) keyboard frame error after rorate device when the keyboard is not docked to bottom.
|
||||
if (((int)[self _systemVersion]) == 7) {
|
||||
UIInterfaceOrientation ori = app.statusBarOrientation;
|
||||
if (_fromOrientation != UIInterfaceOrientationUnknown && _fromOrientation != ori) {
|
||||
switch (ori) {
|
||||
case UIInterfaceOrientationPortrait: {
|
||||
if (CGRectGetMaxY(trans.toFrame) != window.frame.size.height) {
|
||||
trans.toFrame.origin.y -= trans.toFrame.size.height;
|
||||
}
|
||||
} break;
|
||||
case UIInterfaceOrientationPortraitUpsideDown: {
|
||||
if (CGRectGetMinY(trans.toFrame) != 0) {
|
||||
trans.toFrame.origin.y += trans.toFrame.size.height;
|
||||
}
|
||||
} break;
|
||||
case UIInterfaceOrientationLandscapeLeft: {
|
||||
if (CGRectGetMaxX(trans.toFrame) != window.frame.size.width) {
|
||||
trans.toFrame.origin.x -= trans.toFrame.size.width;
|
||||
}
|
||||
} break;
|
||||
case UIInterfaceOrientationLandscapeRight: {
|
||||
if (CGRectGetMinX(trans.toFrame) != 0) {
|
||||
trans.toFrame.origin.x += trans.toFrame.size.width;
|
||||
}
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
trans.toFrame = _observedToFrame;
|
||||
}
|
||||
|
||||
if (window && trans.toFrame.size.width > 0 && trans.toFrame.size.height > 0) {
|
||||
CGRect rect = CGRectIntersection(window.bounds, trans.toFrame);
|
||||
if (!CGRectIsNull(rect) && !CGRectIsEmpty(rect)) {
|
||||
trans.toVisible = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (!CGRectEqualToRect(trans.toFrame, _fromFrame)) {
|
||||
for (id<YYTextKeyboardObserver> observer in _observers.copy) {
|
||||
if ([observer respondsToSelector:@selector(keyboardChangedWithTransition:)]) {
|
||||
[observer keyboardChangedWithTransition:trans];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_hasNotification = NO;
|
||||
_hasObservedChange = NO;
|
||||
_fromFrame = trans.toFrame;
|
||||
_fromVisible = trans.toVisible;
|
||||
_fromOrientation = app.statusBarOrientation;
|
||||
}
|
||||
|
||||
- (CGRect)convertRect:(CGRect)rect toView:(UIView *)view {
|
||||
UIApplication *app = YYTextSharedApplication();
|
||||
if (!app) return CGRectZero;
|
||||
|
||||
if (CGRectIsNull(rect)) return rect;
|
||||
if (CGRectIsInfinite(rect)) return rect;
|
||||
|
||||
UIWindow *mainWindow = app.keyWindow;
|
||||
if (!mainWindow) mainWindow = app.windows.firstObject;
|
||||
if (!mainWindow) { // no window ?!
|
||||
if (view) {
|
||||
[view convertRect:rect fromView:nil];
|
||||
} else {
|
||||
return rect;
|
||||
}
|
||||
}
|
||||
|
||||
rect = [mainWindow convertRect:rect fromWindow:nil];
|
||||
if (!view) return [mainWindow convertRect:rect toWindow:nil];
|
||||
if (view == mainWindow) return rect;
|
||||
|
||||
UIWindow *toWindow = [view isKindOfClass:[UIWindow class]] ? (id)view : view.window;
|
||||
if (!mainWindow || !toWindow) return [mainWindow convertRect:rect toView:view];
|
||||
if (mainWindow == toWindow) return [mainWindow convertRect:rect toView:view];
|
||||
|
||||
// in different window
|
||||
rect = [mainWindow convertRect:rect toView:mainWindow];
|
||||
rect = [toWindow convertRect:rect fromWindow:mainWindow];
|
||||
rect = [view convertRect:rect fromView:toWindow];
|
||||
return rect;
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,571 @@
|
|||
//
|
||||
// YYTextLayout.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/3/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>
|
||||
#import <CoreText/CoreText.h>
|
||||
|
||||
#if __has_include(<YYText/YYText.h>)
|
||||
#import <YYText/YYTextDebugOption.h>
|
||||
#import <YYText/YYTextLine.h>
|
||||
#import <YYText/YYTextInput.h>
|
||||
#else
|
||||
#import "YYTextDebugOption.h"
|
||||
#import "YYTextLine.h"
|
||||
#import "YYTextInput.h"
|
||||
#endif
|
||||
|
||||
@protocol YYTextLinePositionModifier;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The max text container size in layout.
|
||||
*/
|
||||
extern const CGSize YYTextContainerMaxSize;
|
||||
|
||||
/**
|
||||
The YYTextContainer class defines a region in which text is laid out.
|
||||
YYTextLayout class uses one or more YYTextContainer objects to generate layouts.
|
||||
|
||||
A YYTextContainer defines rectangular regions (`size` and `insets`) or
|
||||
nonrectangular shapes (`path`), and you can define exclusion paths inside the
|
||||
text container's bounding rectangle so that text flows around the exclusion
|
||||
path as it is laid out.
|
||||
|
||||
All methods in this class is thread-safe.
|
||||
|
||||
Example:
|
||||
|
||||
┌─────────────────────────────┐ <------- container
|
||||
│ │
|
||||
│ asdfasdfasdfasdfasdfa <------------ container insets
|
||||
│ asdfasdfa asdfasdfa │
|
||||
│ asdfas asdasd │
|
||||
│ asdfa <----------------------- container exclusion path
|
||||
│ asdfas adfasd │
|
||||
│ asdfasdfa asdfasdfa │
|
||||
│ asdfasdfasdfasdfasdfa │
|
||||
│ │
|
||||
└─────────────────────────────┘
|
||||
*/
|
||||
@interface YYTextContainer : NSObject <NSCoding, NSCopying>
|
||||
|
||||
/// Creates a container with the specified size. @param size The size.
|
||||
+ (instancetype)containerWithSize:(CGSize)size;
|
||||
|
||||
/// Creates a container with the specified size and insets. @param size The size. @param insets The text insets.
|
||||
+ (instancetype)containerWithSize:(CGSize)size insets:(UIEdgeInsets)insets;
|
||||
|
||||
/// Creates a container with the specified path. @param size The path.
|
||||
+ (instancetype)containerWithPath:(nullable UIBezierPath *)path;
|
||||
|
||||
/// The constrained size. (if the size is larger than YYTextContainerMaxSize, it will be clipped)
|
||||
@property CGSize size;
|
||||
|
||||
/// The insets for constrained size. The inset value should not be negative. Default is UIEdgeInsetsZero.
|
||||
@property UIEdgeInsets insets;
|
||||
|
||||
/// Custom constrained path. Set this property to ignore `size` and `insets`. Default is nil.
|
||||
@property (nullable, copy) UIBezierPath *path;
|
||||
|
||||
/// An array of `UIBezierPath` for path exclusion. Default is nil.
|
||||
@property (nullable, copy) NSArray<UIBezierPath *> *exclusionPaths;
|
||||
|
||||
/// Path line width. Default is 0;
|
||||
@property CGFloat pathLineWidth;
|
||||
|
||||
/// YES:(PathFillEvenOdd) Text is filled in the area that would be painted if the path were given to CGContextEOFillPath.
|
||||
/// NO: (PathFillWindingNumber) Text is fill in the area that would be painted if the path were given to CGContextFillPath.
|
||||
/// Default is YES;
|
||||
@property (getter=isPathFillEvenOdd) BOOL pathFillEvenOdd;
|
||||
|
||||
/// Whether the text is vertical form (may used for CJK text layout). Default is NO.
|
||||
@property (getter=isVerticalForm) BOOL verticalForm;
|
||||
|
||||
/// Maximum number of rows, 0 means no limit. Default is 0.
|
||||
@property NSUInteger maximumNumberOfRows;
|
||||
|
||||
/// The line truncation type, default is none.
|
||||
@property YYTextTruncationType truncationType;
|
||||
|
||||
/// The truncation token. If nil, the layout will use "…" instead. Default is nil.
|
||||
@property (nullable, copy) NSAttributedString *truncationToken;
|
||||
|
||||
/// This modifier is applied to the lines before the layout is completed,
|
||||
/// give you a chance to modify the line position. Default is nil.
|
||||
@property (nullable, copy) id<YYTextLinePositionModifier> linePositionModifier;
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
The YYTextLinePositionModifier protocol declares the required method to modify
|
||||
the line position in text layout progress. See `YYTextLinePositionSimpleModifier` for example.
|
||||
*/
|
||||
@protocol YYTextLinePositionModifier <NSObject, NSCopying>
|
||||
@required
|
||||
/**
|
||||
This method will called before layout is completed. The method should be thread-safe.
|
||||
@param lines An array of YYTextLine.
|
||||
@param text The full text.
|
||||
@param container The layout container.
|
||||
*/
|
||||
- (void)modifyLines:(NSArray<YYTextLine *> *)lines fromText:(NSAttributedString *)text inContainer:(YYTextContainer *)container;
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
A simple implementation of `YYTextLinePositionModifier`. It can fix each line's position
|
||||
to a specified value, lets each line of height be the same.
|
||||
*/
|
||||
@interface YYTextLinePositionSimpleModifier : NSObject <YYTextLinePositionModifier>
|
||||
@property (assign) CGFloat fixedLineHeight; ///< The fixed line height (distance between two baseline).
|
||||
@end
|
||||
|
||||
|
||||
|
||||
/**
|
||||
YYTextLayout class is a readonly class stores text layout result.
|
||||
All the property in this class is readonly, and should not be changed.
|
||||
The methods in this class is thread-safe (except some of the draw methods).
|
||||
|
||||
example: (layout with a circle exclusion path)
|
||||
|
||||
┌──────────────────────────┐ <------ container
|
||||
│ [--------Line0--------] │ <- Row0
|
||||
│ [--------Line1--------] │ <- Row1
|
||||
│ [-Line2-] [-Line3-] │ <- Row2
|
||||
│ [-Line4] [Line5-] │ <- Row3
|
||||
│ [-Line6-] [-Line7-] │ <- Row4
|
||||
│ [--------Line8--------] │ <- Row5
|
||||
│ [--------Line9--------] │ <- Row6
|
||||
└──────────────────────────┘
|
||||
*/
|
||||
@interface YYTextLayout : NSObject <NSCoding>
|
||||
|
||||
|
||||
#pragma mark - Generate text layout
|
||||
///=============================================================================
|
||||
/// @name Generate text layout
|
||||
///=============================================================================
|
||||
|
||||
/**
|
||||
Generate a layout with the given container size and text.
|
||||
|
||||
@param size The text container's size
|
||||
@param text The text (if nil, returns nil).
|
||||
@return A new layout, or nil when an error occurs.
|
||||
*/
|
||||
+ (nullable YYTextLayout *)layoutWithContainerSize:(CGSize)size text:(NSAttributedString *)text;
|
||||
|
||||
/**
|
||||
Generate a layout with the given container and text.
|
||||
|
||||
@param container The text container (if nil, returns nil).
|
||||
@param text The text (if nil, returns nil).
|
||||
@return A new layout, or nil when an error occurs.
|
||||
*/
|
||||
+ (nullable YYTextLayout *)layoutWithContainer:(YYTextContainer *)container text:(NSAttributedString *)text;
|
||||
|
||||
/**
|
||||
Generate a layout with the given container and text.
|
||||
|
||||
@param container The text container (if nil, returns nil).
|
||||
@param text The text (if nil, returns nil).
|
||||
@param range The text range (if out of range, returns nil). If the
|
||||
length of the range is 0, it means the length is no limit.
|
||||
@return A new layout, or nil when an error occurs.
|
||||
*/
|
||||
+ (nullable YYTextLayout *)layoutWithContainer:(YYTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range;
|
||||
|
||||
/**
|
||||
Generate layouts with the given containers and text.
|
||||
|
||||
@param containers An array of YYTextContainer object (if nil, returns nil).
|
||||
@param text The text (if nil, returns nil).
|
||||
@return An array of YYTextLayout object (the count is same as containers),
|
||||
or nil when an error occurs.
|
||||
*/
|
||||
+ (nullable NSArray<YYTextLayout *> *)layoutWithContainers:(NSArray<YYTextContainer *> *)containers
|
||||
text:(NSAttributedString *)text;
|
||||
|
||||
/**
|
||||
Generate layouts with the given containers and text.
|
||||
|
||||
@param containers An array of YYTextContainer object (if nil, returns nil).
|
||||
@param text The text (if nil, returns nil).
|
||||
@param range The text range (if out of range, returns nil). If the
|
||||
length of the range is 0, it means the length is no limit.
|
||||
@return An array of YYTextLayout object (the count is same as containers),
|
||||
or nil when an error occurs.
|
||||
*/
|
||||
+ (nullable NSArray<YYTextLayout *> *)layoutWithContainers:(NSArray<YYTextContainer *> *)containers
|
||||
text:(NSAttributedString *)text
|
||||
range:(NSRange)range;
|
||||
|
||||
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
|
||||
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;
|
||||
|
||||
|
||||
#pragma mark - Text layout attributes
|
||||
///=============================================================================
|
||||
/// @name Text layout attributes
|
||||
///=============================================================================
|
||||
|
||||
///< The text container
|
||||
@property (nonatomic, strong, readonly) YYTextContainer *container;
|
||||
///< The full text
|
||||
@property (nonatomic, strong, readonly) NSAttributedString *text;
|
||||
///< The text range in full text
|
||||
@property (nonatomic, readonly) NSRange range;
|
||||
///< CTFrameSetter
|
||||
@property (nonatomic, readonly) CTFramesetterRef frameSetter;
|
||||
///< CTFrame
|
||||
@property (nonatomic, readonly) CTFrameRef frame;
|
||||
///< Array of `YYTextLine`, no truncated
|
||||
@property (nonatomic, strong, readonly) NSArray<YYTextLine *> *lines;
|
||||
///< YYTextLine with truncated token, or nil
|
||||
@property (nullable, nonatomic, strong, readonly) YYTextLine *truncatedLine;
|
||||
///< Array of `YYTextAttachment`
|
||||
@property (nullable, nonatomic, strong, readonly) NSArray<YYTextAttachment *> *attachments;
|
||||
///< Array of NSRange(wrapped by NSValue) in text
|
||||
@property (nullable, nonatomic, strong, readonly) NSArray<NSValue *> *attachmentRanges;
|
||||
///< Array of CGRect(wrapped by NSValue) in container
|
||||
@property (nullable, nonatomic, strong, readonly) NSArray<NSValue *> *attachmentRects;
|
||||
///< Set of Attachment (UIImage/UIView/CALayer)
|
||||
@property (nullable, nonatomic, strong, readonly) NSSet *attachmentContentsSet;
|
||||
///< Number of rows
|
||||
@property (nonatomic, readonly) NSUInteger rowCount;
|
||||
///< Visible text range
|
||||
@property (nonatomic, readonly) NSRange visibleRange;
|
||||
///< Bounding rect (glyphs)
|
||||
@property (nonatomic, readonly) CGRect textBoundingRect;
|
||||
///< Bounding size (glyphs and insets, ceil to pixel)
|
||||
@property (nonatomic, readonly) CGSize textBoundingSize;
|
||||
///< Has highlight attribute
|
||||
@property (nonatomic, readonly) BOOL containsHighlight;
|
||||
///< Has block border attribute
|
||||
@property (nonatomic, readonly) BOOL needDrawBlockBorder;
|
||||
///< Has background border attribute
|
||||
@property (nonatomic, readonly) BOOL needDrawBackgroundBorder;
|
||||
///< Has shadow attribute
|
||||
@property (nonatomic, readonly) BOOL needDrawShadow;
|
||||
///< Has underline attribute
|
||||
@property (nonatomic, readonly) BOOL needDrawUnderline;
|
||||
///< Has visible text
|
||||
@property (nonatomic, readonly) BOOL needDrawText;
|
||||
///< Has attachment attribute
|
||||
@property (nonatomic, readonly) BOOL needDrawAttachment;
|
||||
///< Has inner shadow attribute
|
||||
@property (nonatomic, readonly) BOOL needDrawInnerShadow;
|
||||
///< Has strickthrough attribute
|
||||
@property (nonatomic, readonly) BOOL needDrawStrikethrough;
|
||||
///< Has border attribute
|
||||
@property (nonatomic, readonly) BOOL needDrawBorder;
|
||||
|
||||
|
||||
#pragma mark - Query information from text layout
|
||||
///=============================================================================
|
||||
/// @name Query information from text layout
|
||||
///=============================================================================
|
||||
|
||||
/**
|
||||
The first line index for row.
|
||||
|
||||
@param row A row index.
|
||||
@return The line index, or NSNotFound if not found.
|
||||
*/
|
||||
- (NSUInteger)lineIndexForRow:(NSUInteger)row;
|
||||
|
||||
/**
|
||||
The number of lines for row.
|
||||
|
||||
@param row A row index.
|
||||
@return The number of lines, or NSNotFound when an error occurs.
|
||||
*/
|
||||
- (NSUInteger)lineCountForRow:(NSUInteger)row;
|
||||
|
||||
/**
|
||||
The row index for line.
|
||||
|
||||
@param line A row index.
|
||||
|
||||
@return The row index, or NSNotFound if not found.
|
||||
*/
|
||||
- (NSUInteger)rowIndexForLine:(NSUInteger)line;
|
||||
|
||||
/**
|
||||
The line index for a specified point.
|
||||
|
||||
@discussion It returns NSNotFound if there's no text at the point.
|
||||
|
||||
@param point A point in the container.
|
||||
@return The line index, or NSNotFound if not found.
|
||||
*/
|
||||
- (NSUInteger)lineIndexForPoint:(CGPoint)point;
|
||||
|
||||
/**
|
||||
The line index closest to a specified point.
|
||||
|
||||
@param point A point in the container.
|
||||
@return The line index, or NSNotFound if no line exist in layout.
|
||||
*/
|
||||
- (NSUInteger)closestLineIndexForPoint:(CGPoint)point;
|
||||
|
||||
/**
|
||||
The offset in container for a text position in a specified line.
|
||||
|
||||
@discussion The offset is the text position's baseline point.x.
|
||||
If the container is vertical form, the offset is the baseline point.y;
|
||||
|
||||
@param position The text position in string.
|
||||
@param lineIndex The line index.
|
||||
@return The offset in container, or CGFLOAT_MAX if not found.
|
||||
*/
|
||||
- (CGFloat)offsetForTextPosition:(NSUInteger)position lineIndex:(NSUInteger)lineIndex;
|
||||
|
||||
/**
|
||||
The text position for a point in a specified line.
|
||||
|
||||
@discussion This method just call CTLineGetStringIndexForPosition() and does
|
||||
NOT consider the emoji, line break character, binding text...
|
||||
|
||||
@param point A point in the container.
|
||||
@param lineIndex The line index.
|
||||
@return The text position, or NSNotFound if not found.
|
||||
*/
|
||||
- (NSUInteger)textPositionForPoint:(CGPoint)point lineIndex:(NSUInteger)lineIndex;
|
||||
|
||||
/**
|
||||
The closest text position to a specified point.
|
||||
|
||||
@discussion This method takes into account the restrict of emoji, line break
|
||||
character, binding text and text affinity.
|
||||
|
||||
@param point A point in the container.
|
||||
@return A text position, or nil if not found.
|
||||
*/
|
||||
- (nullable YYTextPosition *)closestPositionToPoint:(CGPoint)point;
|
||||
|
||||
/**
|
||||
Returns the new position when moving selection grabber in text view.
|
||||
|
||||
@discussion There are two grabber in the text selection period, user can only
|
||||
move one grabber at the same time.
|
||||
|
||||
@param point A point in the container.
|
||||
@param oldPosition The old text position for the moving grabber.
|
||||
@param otherPosition The other position in text selection view.
|
||||
|
||||
@return A text position, or nil if not found.
|
||||
*/
|
||||
- (nullable YYTextPosition *)positionForPoint:(CGPoint)point
|
||||
oldPosition:(YYTextPosition *)oldPosition
|
||||
otherPosition:(YYTextPosition *)otherPosition;
|
||||
|
||||
/**
|
||||
Returns the character or range of characters that is at a given point in the container.
|
||||
If there is no text at the point, returns nil.
|
||||
|
||||
@discussion This method takes into account the restrict of emoji, line break
|
||||
character, binding text and text affinity.
|
||||
|
||||
@param point A point in the container.
|
||||
@return An object representing a range that encloses a character (or characters)
|
||||
at point. Or nil if not found.
|
||||
*/
|
||||
- (nullable YYTextRange *)textRangeAtPoint:(CGPoint)point;
|
||||
|
||||
/**
|
||||
Returns the closest character or range of characters that is at a given point in
|
||||
the container.
|
||||
|
||||
@discussion This method takes into account the restrict of emoji, line break
|
||||
character, binding text and text affinity.
|
||||
|
||||
@param point A point in the container.
|
||||
@return An object representing a range that encloses a character (or characters)
|
||||
at point. Or nil if not found.
|
||||
*/
|
||||
- (nullable YYTextRange *)closestTextRangeAtPoint:(CGPoint)point;
|
||||
|
||||
/**
|
||||
If the position is inside an emoji, composed character sequences, line break '\\r\\n'
|
||||
or custom binding range, then returns the range by extend the position. Otherwise,
|
||||
returns a zero length range from the position.
|
||||
|
||||
@param position A text-position object that identifies a location in layout.
|
||||
|
||||
@return A text-range object that extend the position. Or nil if an error occurs
|
||||
*/
|
||||
- (nullable YYTextRange *)textRangeByExtendingPosition:(YYTextPosition *)position;
|
||||
|
||||
/**
|
||||
Returns a text range at a given offset in a specified direction from another
|
||||
text position to its farthest extent in a certain direction of layout.
|
||||
|
||||
@param position A text-position object that identifies a location in layout.
|
||||
@param direction A constant that indicates a direction of layout (right, left, up, down).
|
||||
@param offset A character offset from position.
|
||||
|
||||
@return A text-range object that represents the distance from position to the
|
||||
farthest extent in direction. Or nil if an error occurs.
|
||||
*/
|
||||
- (nullable YYTextRange *)textRangeByExtendingPosition:(YYTextPosition *)position
|
||||
inDirection:(UITextLayoutDirection)direction
|
||||
offset:(NSInteger)offset;
|
||||
|
||||
/**
|
||||
Returns the line index for a given text position.
|
||||
|
||||
@discussion This method takes into account the text affinity.
|
||||
|
||||
@param position A text-position object that identifies a location in layout.
|
||||
@return The line index, or NSNotFound if not found.
|
||||
*/
|
||||
- (NSUInteger)lineIndexForPosition:(YYTextPosition *)position;
|
||||
|
||||
/**
|
||||
Returns the baseline position for a given text position.
|
||||
|
||||
@param position An object that identifies a location in the layout.
|
||||
@return The baseline position for text, or CGPointZero if not found.
|
||||
*/
|
||||
- (CGPoint)linePositionForPosition:(YYTextPosition *)position;
|
||||
|
||||
/**
|
||||
Returns a rectangle used to draw the caret at a given insertion point.
|
||||
|
||||
@param position An object that identifies a location in the layout.
|
||||
@return A rectangle that defines the area for drawing the caret. The width is
|
||||
always zero in normal container, the height is always zero in vertical form container.
|
||||
If not found, it returns CGRectNull.
|
||||
*/
|
||||
- (CGRect)caretRectForPosition:(YYTextPosition *)position;
|
||||
|
||||
/**
|
||||
Returns the first rectangle that encloses a range of text in the layout.
|
||||
|
||||
@param range An object that represents a range of text in layout.
|
||||
|
||||
@return The first rectangle in a range of text. You might use this rectangle to
|
||||
draw a correction rectangle. The "first" in the name refers the rectangle
|
||||
enclosing the first line when the range encompasses multiple lines of text.
|
||||
If not found, it returns CGRectNull.
|
||||
*/
|
||||
- (CGRect)firstRectForRange:(YYTextRange *)range;
|
||||
|
||||
/**
|
||||
Returns the rectangle union that encloses a range of text in the layout.
|
||||
|
||||
@param range An object that represents a range of text in layout.
|
||||
|
||||
@return A rectangle that defines the area than encloses the range.
|
||||
If not found, it returns CGRectNull.
|
||||
*/
|
||||
- (CGRect)rectForRange:(YYTextRange *)range;
|
||||
|
||||
/**
|
||||
Returns an array of selection rects corresponding to the range of text.
|
||||
The start and end rect can be used to show grabber.
|
||||
|
||||
@param range An object representing a range in text.
|
||||
@return An array of `YYTextSelectionRect` objects that encompass the selection.
|
||||
If not found, the array is empty.
|
||||
*/
|
||||
- (NSArray<YYTextSelectionRect *> *)selectionRectsForRange:(YYTextRange *)range;
|
||||
|
||||
/**
|
||||
Returns an array of selection rects corresponding to the range of text.
|
||||
|
||||
@param range An object representing a range in text.
|
||||
@return An array of `YYTextSelectionRect` objects that encompass the selection.
|
||||
If not found, the array is empty.
|
||||
*/
|
||||
- (NSArray<YYTextSelectionRect *> *)selectionRectsWithoutStartAndEndForRange:(YYTextRange *)range;
|
||||
|
||||
/**
|
||||
Returns the start and end selection rects corresponding to the range of text.
|
||||
The start and end rect can be used to show grabber.
|
||||
|
||||
@param range An object representing a range in text.
|
||||
@return An array of `YYTextSelectionRect` objects contains the start and end to
|
||||
the selection. If not found, the array is empty.
|
||||
*/
|
||||
- (NSArray<YYTextSelectionRect *> *)selectionRectsWithOnlyStartAndEndForRange:(YYTextRange *)range;
|
||||
|
||||
|
||||
#pragma mark - Draw text layout
|
||||
///=============================================================================
|
||||
/// @name Draw text layout
|
||||
///=============================================================================
|
||||
|
||||
/**
|
||||
Draw the layout and show the attachments.
|
||||
|
||||
@discussion If the `view` parameter is not nil, then the attachment views will
|
||||
add to this `view`, and if the `layer` parameter is not nil, then the attachment
|
||||
layers will add to this `layer`.
|
||||
|
||||
@warning This method should be called on main thread if `view` or `layer` parameter
|
||||
is not nil and there's UIView or CALayer attachments in layout.
|
||||
Otherwise, it can be called on any thread.
|
||||
|
||||
@param context The draw context. Pass nil to avoid text and image drawing.
|
||||
@param size The context size.
|
||||
@param point The point at which to draw the layout.
|
||||
@param view The attachment views will add to this view.
|
||||
@param layer The attachment layers will add to this layer.
|
||||
@param debug The debug option. Pass nil to avoid debug drawing.
|
||||
@param cancel The cancel checker block. It will be called in drawing progress.
|
||||
If it returns YES, the further draw progress will be canceled.
|
||||
Pass nil to ignore this feature.
|
||||
*/
|
||||
- (void)drawInContext:(nullable CGContextRef)context
|
||||
size:(CGSize)size
|
||||
point:(CGPoint)point
|
||||
view:(nullable UIView *)view
|
||||
layer:(nullable CALayer *)layer
|
||||
debug:(nullable YYTextDebugOption *)debug
|
||||
cancel:(nullable BOOL (^)(void))cancel;
|
||||
|
||||
/**
|
||||
Draw the layout text and image (without view or layer attachments).
|
||||
|
||||
@discussion This method is thread safe and can be called on any thread.
|
||||
|
||||
@param context The draw context. Pass nil to avoid text and image drawing.
|
||||
@param size The context size.
|
||||
@param debug The debug option. Pass nil to avoid debug drawing.
|
||||
*/
|
||||
- (void)drawInContext:(nullable CGContextRef)context
|
||||
size:(CGSize)size
|
||||
debug:(nullable YYTextDebugOption *)debug;
|
||||
|
||||
/**
|
||||
Show view and layer attachments.
|
||||
|
||||
@warning This method must be called on main thread.
|
||||
|
||||
@param view The attachment views will add to this view.
|
||||
@param layer The attachment layers will add to this layer.
|
||||
*/
|
||||
- (void)addAttachmentToView:(nullable UIView *)view layer:(nullable CALayer *)layer;
|
||||
|
||||
/**
|
||||
Remove attachment views and layers from their super container.
|
||||
|
||||
@warning This method must be called on main thread.
|
||||
*/
|
||||
- (void)removeAttachmentFromViewAndLayer;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,84 @@
|
|||
//
|
||||
// YYTextLine.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/3/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 <CoreText/CoreText.h>
|
||||
|
||||
#if __has_include(<YYText/YYText.h>)
|
||||
#import <YYText/YYTextAttribute.h>
|
||||
#else
|
||||
#import "YYTextAttribute.h"
|
||||
#endif
|
||||
|
||||
@class YYTextRunGlyphRange;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
A text line object wrapped `CTLineRef`, see `YYTextLayout` for more.
|
||||
*/
|
||||
@interface YYTextLine : NSObject
|
||||
|
||||
+ (instancetype)lineWithCTLine:(CTLineRef)CTLine position:(CGPoint)position vertical:(BOOL)isVertical;
|
||||
|
||||
@property (nonatomic) NSUInteger index; ///< line index
|
||||
@property (nonatomic) NSUInteger row; ///< line row
|
||||
@property (nullable, nonatomic, strong) NSArray<NSArray<YYTextRunGlyphRange *> *> *verticalRotateRange; ///< Run rotate range
|
||||
|
||||
@property (nonatomic, readonly) CTLineRef CTLine; ///< CoreText line
|
||||
@property (nonatomic, readonly) NSRange range; ///< string range
|
||||
@property (nonatomic, readonly) BOOL vertical; ///< vertical form
|
||||
|
||||
@property (nonatomic, readonly) CGRect bounds; ///< bounds (ascent + descent)
|
||||
@property (nonatomic, readonly) CGSize size; ///< bounds.size
|
||||
@property (nonatomic, readonly) CGFloat width; ///< bounds.size.width
|
||||
@property (nonatomic, readonly) CGFloat height; ///< bounds.size.height
|
||||
@property (nonatomic, readonly) CGFloat top; ///< bounds.origin.y
|
||||
@property (nonatomic, readonly) CGFloat bottom; ///< bounds.origin.y + bounds.size.height
|
||||
@property (nonatomic, readonly) CGFloat left; ///< bounds.origin.x
|
||||
@property (nonatomic, readonly) CGFloat right; ///< bounds.origin.x + bounds.size.width
|
||||
|
||||
@property (nonatomic) CGPoint position; ///< baseline position
|
||||
@property (nonatomic, readonly) CGFloat ascent; ///< line ascent
|
||||
@property (nonatomic, readonly) CGFloat descent; ///< line descent
|
||||
@property (nonatomic, readonly) CGFloat leading; ///< line leading
|
||||
@property (nonatomic, readonly) CGFloat lineWidth; ///< line width
|
||||
@property (nonatomic, readonly) CGFloat trailingWhitespaceWidth;
|
||||
|
||||
@property (nullable, nonatomic, readonly) NSArray<YYTextAttachment *> *attachments; ///< YYTextAttachment
|
||||
@property (nullable, nonatomic, readonly) NSArray<NSValue *> *attachmentRanges; ///< NSRange(NSValue)
|
||||
@property (nullable, nonatomic, readonly) NSArray<NSValue *> *attachmentRects; ///< CGRect(NSValue)
|
||||
|
||||
@end
|
||||
|
||||
|
||||
typedef NS_ENUM(NSUInteger, YYTextRunGlyphDrawMode) {
|
||||
/// No rotate.
|
||||
YYTextRunGlyphDrawModeHorizontal = 0,
|
||||
|
||||
/// Rotate vertical for single glyph.
|
||||
YYTextRunGlyphDrawModeVerticalRotate = 1,
|
||||
|
||||
/// Rotate vertical for single glyph, and move the glyph to a better position,
|
||||
/// such as fullwidth punctuation.
|
||||
YYTextRunGlyphDrawModeVerticalRotateMove = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
A range in CTRun, used for vertical form.
|
||||
*/
|
||||
@interface YYTextRunGlyphRange : NSObject
|
||||
@property (nonatomic) NSRange glyphRangeInRun;
|
||||
@property (nonatomic) YYTextRunGlyphDrawMode drawMode;
|
||||
+ (instancetype)rangeWithRange:(NSRange)range drawMode:(YYTextRunGlyphDrawMode)mode;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,167 @@
|
|||
//
|
||||
// YYYTextLine.m
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/3/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 "YYTextLine.h"
|
||||
#import "YYTextUtilities.h"
|
||||
|
||||
|
||||
@implementation YYTextLine {
|
||||
CGFloat _firstGlyphPos; // first glyph position for baseline, typically 0.
|
||||
}
|
||||
|
||||
+ (instancetype)lineWithCTLine:(CTLineRef)CTLine position:(CGPoint)position vertical:(BOOL)isVertical {
|
||||
if (!CTLine) return nil;
|
||||
YYTextLine *line = [self new];
|
||||
line->_position = position;
|
||||
line->_vertical = isVertical;
|
||||
[line setCTLine:CTLine];
|
||||
return line;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (_CTLine) CFRelease(_CTLine);
|
||||
}
|
||||
|
||||
- (void)setCTLine:(_Nonnull CTLineRef)CTLine {
|
||||
if (_CTLine != CTLine) {
|
||||
if (CTLine) CFRetain(CTLine);
|
||||
if (_CTLine) CFRelease(_CTLine);
|
||||
_CTLine = CTLine;
|
||||
if (_CTLine) {
|
||||
_lineWidth = CTLineGetTypographicBounds(_CTLine, &_ascent, &_descent, &_leading);
|
||||
CFRange range = CTLineGetStringRange(_CTLine);
|
||||
_range = NSMakeRange(range.location, range.length);
|
||||
if (CTLineGetGlyphCount(_CTLine) > 0) {
|
||||
CFArrayRef runs = CTLineGetGlyphRuns(_CTLine);
|
||||
CTRunRef run = CFArrayGetValueAtIndex(runs, 0);
|
||||
CGPoint pos;
|
||||
CTRunGetPositions(run, CFRangeMake(0, 1), &pos);
|
||||
_firstGlyphPos = pos.x;
|
||||
} else {
|
||||
_firstGlyphPos = 0;
|
||||
}
|
||||
_trailingWhitespaceWidth = CTLineGetTrailingWhitespaceWidth(_CTLine);
|
||||
} else {
|
||||
_lineWidth = _ascent = _descent = _leading = _firstGlyphPos = _trailingWhitespaceWidth = 0;
|
||||
_range = NSMakeRange(0, 0);
|
||||
}
|
||||
[self reloadBounds];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setPosition:(CGPoint)position {
|
||||
_position = position;
|
||||
[self reloadBounds];
|
||||
}
|
||||
|
||||
- (void)reloadBounds {
|
||||
if (_vertical) {
|
||||
_bounds = CGRectMake(_position.x - _descent, _position.y, _ascent + _descent, _lineWidth);
|
||||
_bounds.origin.y += _firstGlyphPos;
|
||||
} else {
|
||||
_bounds = CGRectMake(_position.x, _position.y - _ascent, _lineWidth, _ascent + _descent);
|
||||
_bounds.origin.x += _firstGlyphPos;
|
||||
}
|
||||
|
||||
_attachments = nil;
|
||||
_attachmentRanges = nil;
|
||||
_attachmentRects = nil;
|
||||
if (!_CTLine) return;
|
||||
CFArrayRef runs = CTLineGetGlyphRuns(_CTLine);
|
||||
NSUInteger runCount = CFArrayGetCount(runs);
|
||||
if (runCount == 0) return;
|
||||
|
||||
NSMutableArray *attachments = [NSMutableArray new];
|
||||
NSMutableArray *attachmentRanges = [NSMutableArray new];
|
||||
NSMutableArray *attachmentRects = [NSMutableArray new];
|
||||
for (NSUInteger r = 0; r < runCount; r++) {
|
||||
CTRunRef run = CFArrayGetValueAtIndex(runs, r);
|
||||
CFIndex glyphCount = CTRunGetGlyphCount(run);
|
||||
if (glyphCount == 0) continue;
|
||||
NSDictionary *attrs = (id)CTRunGetAttributes(run);
|
||||
YYTextAttachment *attachment = attrs[YYTextAttachmentAttributeName];
|
||||
if (attachment) {
|
||||
CGPoint runPosition = CGPointZero;
|
||||
CTRunGetPositions(run, CFRangeMake(0, 1), &runPosition);
|
||||
|
||||
CGFloat ascent, descent, leading, runWidth;
|
||||
CGRect runTypoBounds;
|
||||
runWidth = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, &leading);
|
||||
|
||||
if (_vertical) {
|
||||
YYTEXT_SWAP(runPosition.x, runPosition.y);
|
||||
runPosition.y = _position.y + runPosition.y;
|
||||
runTypoBounds = CGRectMake(_position.x + runPosition.x - descent, runPosition.y , ascent + descent, runWidth);
|
||||
} else {
|
||||
runPosition.x += _position.x;
|
||||
runPosition.y = _position.y - runPosition.y;
|
||||
runTypoBounds = CGRectMake(runPosition.x, runPosition.y - ascent, runWidth, ascent + descent);
|
||||
}
|
||||
|
||||
NSRange runRange = YYTextNSRangeFromCFRange(CTRunGetStringRange(run));
|
||||
[attachments addObject:attachment];
|
||||
[attachmentRanges addObject:[NSValue valueWithRange:runRange]];
|
||||
[attachmentRects addObject:[NSValue valueWithCGRect:runTypoBounds]];
|
||||
}
|
||||
}
|
||||
_attachments = attachments.count ? attachments : nil;
|
||||
_attachmentRanges = attachmentRanges.count ? attachmentRanges : nil;
|
||||
_attachmentRects = attachmentRects.count ? attachmentRects : nil;
|
||||
}
|
||||
|
||||
- (CGSize)size {
|
||||
return _bounds.size;
|
||||
}
|
||||
|
||||
- (CGFloat)width {
|
||||
return CGRectGetWidth(_bounds);
|
||||
}
|
||||
|
||||
- (CGFloat)height {
|
||||
return CGRectGetHeight(_bounds);
|
||||
}
|
||||
|
||||
- (CGFloat)top {
|
||||
return CGRectGetMinY(_bounds);
|
||||
}
|
||||
|
||||
- (CGFloat)bottom {
|
||||
return CGRectGetMaxY(_bounds);
|
||||
}
|
||||
|
||||
- (CGFloat)left {
|
||||
return CGRectGetMinX(_bounds);
|
||||
}
|
||||
|
||||
- (CGFloat)right {
|
||||
return CGRectGetMaxX(_bounds);
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
NSMutableString *desc = @"".mutableCopy;
|
||||
NSRange range = self.range;
|
||||
[desc appendFormat:@"<YYTextLine: %p> row:%zd range:%tu,%tu",self, self.row, range.location, range.length];
|
||||
[desc appendFormat:@" position:%@",NSStringFromCGPoint(self.position)];
|
||||
[desc appendFormat:@" bounds:%@",NSStringFromCGRect(self.bounds)];
|
||||
return desc;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation YYTextRunGlyphRange
|
||||
+ (instancetype)rangeWithRange:(NSRange)range drawMode:(YYTextRunGlyphDrawMode)mode {
|
||||
YYTextRunGlyphRange *one = [self new];
|
||||
one.glyphRangeInRun = range;
|
||||
one.drawMode = mode;
|
||||
return one;
|
||||
}
|
||||
@end
|
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// YYTextMagnifier.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/2/25.
|
||||
// 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(<YYText/YYText.h>)
|
||||
#import <YYText/YYTextAttribute.h>
|
||||
#else
|
||||
#import "YYTextAttribute.h"
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// Magnifier type
|
||||
typedef NS_ENUM(NSInteger, YYTextMagnifierType) {
|
||||
YYTextMagnifierTypeCaret, ///< Circular magnifier
|
||||
YYTextMagnifierTypeRanged, ///< Round rectangle magnifier
|
||||
};
|
||||
|
||||
/**
|
||||
A magnifier view which can be displayed in `YYTextEffectWindow`.
|
||||
|
||||
@discussion Use `magnifierWithType:` to create instance.
|
||||
Typically, you should not use this class directly.
|
||||
*/
|
||||
@interface YYTextMagnifier : UIView
|
||||
|
||||
/// Create a mangifier with the specified type. @param type The magnifier type.
|
||||
+ (id)magnifierWithType:(YYTextMagnifierType)type;
|
||||
|
||||
@property (nonatomic, readonly) YYTextMagnifierType type; ///< Type of magnifier
|
||||
@property (nonatomic, readonly) CGSize fitSize; ///< The 'best' size for magnifier view.
|
||||
@property (nonatomic, readonly) CGSize snapshotSize; ///< The 'best' snapshot image size for magnifier.
|
||||
@property (nullable, nonatomic, strong) UIImage *snapshot; ///< The image in magnifier (readwrite).
|
||||
|
||||
@property (nullable, nonatomic, weak) UIView *hostView; ///< The coordinate based view.
|
||||
@property (nonatomic) CGPoint hostCaptureCenter; ///< The snapshot capture center in `hostView`.
|
||||
@property (nonatomic) CGPoint hostPopoverCenter; ///< The popover center in `hostView`.
|
||||
@property (nonatomic) BOOL hostVerticalForm; ///< The host view is vertical form.
|
||||
@property (nonatomic) BOOL captureDisabled; ///< A hint for `YYTextEffectWindow` to disable capture.
|
||||
@property (nonatomic) BOOL captureFadeAnimation; ///< Show fade animation when the snapshot image changed.
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,355 @@
|
|||
//
|
||||
// YYTextMagnifier.m
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/2/25.
|
||||
// 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 "YYTextMagnifier.h"
|
||||
#import "YYTextUtilities.h"
|
||||
|
||||
#define kCaptureDisableFadeTime 0.1
|
||||
|
||||
|
||||
@interface _YYTextMagnifierCaret : YYTextMagnifier {
|
||||
UIImageView *_contentView;
|
||||
UIImageView *_coverView;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation _YYTextMagnifierCaret
|
||||
|
||||
#define kMultiple 1.2
|
||||
#define kDiameter 113.0
|
||||
#define kPadding 7.0
|
||||
#define kSize CGSizeMake(kDiameter + kPadding * 2, kDiameter + kPadding * 2)
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
_contentView = [UIImageView new];
|
||||
_contentView.frame = CGRectMake(kPadding, kPadding, kDiameter, kDiameter);
|
||||
_contentView.layer.cornerRadius = kDiameter / 2;
|
||||
_contentView.clipsToBounds = YES;
|
||||
[self addSubview:_contentView];
|
||||
|
||||
_coverView = [UIImageView new];
|
||||
_coverView.frame = (CGRect){.origin = CGPointZero, .size = kSize};
|
||||
_coverView.image = [self.class coverImage];
|
||||
[self addSubview:_coverView];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [self initWithFrame:CGRectZero];
|
||||
self.frame = (CGRect){.size = [self sizeThatFits:CGSizeZero]};
|
||||
return self;
|
||||
}
|
||||
|
||||
- (YYTextMagnifierType)type {
|
||||
return YYTextMagnifierTypeCaret;
|
||||
}
|
||||
|
||||
- (CGSize)sizeThatFits:(CGSize)size {
|
||||
return kSize;
|
||||
}
|
||||
|
||||
- (void)setSnapshot:(UIImage *)snapshot {
|
||||
if (self.captureFadeAnimation) {
|
||||
[_contentView.layer removeAnimationForKey:@"contents"];
|
||||
CABasicAnimation *animation = [CABasicAnimation animation];
|
||||
animation.duration = kCaptureDisableFadeTime;
|
||||
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
|
||||
[_contentView.layer addAnimation:animation forKey:@"contents"];
|
||||
}
|
||||
_contentView.image = snapshot;
|
||||
}
|
||||
|
||||
- (UIImage *)snapshot {
|
||||
return _contentView.image;
|
||||
}
|
||||
|
||||
- (CGSize)snapshotSize {
|
||||
CGFloat length = floor(kDiameter / 1.2);
|
||||
return CGSizeMake(length, length);
|
||||
}
|
||||
|
||||
- (CGSize)fitSize {
|
||||
return [self sizeThatFits:CGSizeZero];
|
||||
}
|
||||
|
||||
+ (UIImage *)coverImage {
|
||||
static UIImage *image;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
CGSize size = kSize;
|
||||
CGRect rect = (CGRect) {.size = size, .origin = CGPointZero};
|
||||
rect = CGRectInset(rect, kPadding, kPadding);
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
|
||||
CGPathRef boxPath = CGPathCreateWithRect(CGRectMake(0, 0, size.width, size.height), NULL);
|
||||
CGPathRef fillPath = CGPathCreateWithEllipseInRect(rect, NULL);
|
||||
CGPathRef strokePath = CGPathCreateWithEllipseInRect(YYTextCGRectPixelHalf(rect), NULL);
|
||||
|
||||
// inner shadow
|
||||
CGContextSaveGState(context); {
|
||||
CGFloat blurRadius = 25;
|
||||
CGSize offset = CGSizeMake(0, 15);
|
||||
CGColorRef shadowColor = [UIColor colorWithWhite:0 alpha:0.16].CGColor;
|
||||
CGColorRef opaqueShadowColor = CGColorCreateCopyWithAlpha(shadowColor, 1.0);
|
||||
CGContextAddPath(context, fillPath);
|
||||
CGContextClip(context);
|
||||
CGContextSetAlpha(context, CGColorGetAlpha(shadowColor));
|
||||
CGContextBeginTransparencyLayer(context, NULL); {
|
||||
CGContextSetShadowWithColor(context, offset, blurRadius, opaqueShadowColor);
|
||||
CGContextSetBlendMode(context, kCGBlendModeSourceOut);
|
||||
CGContextSetFillColorWithColor(context, opaqueShadowColor);
|
||||
CGContextAddPath(context, fillPath);
|
||||
CGContextFillPath(context);
|
||||
} CGContextEndTransparencyLayer(context);
|
||||
CGColorRelease(opaqueShadowColor);
|
||||
} CGContextRestoreGState(context);
|
||||
|
||||
// outer shadow
|
||||
CGContextSaveGState(context); {
|
||||
CGContextAddPath(context, boxPath);
|
||||
CGContextAddPath(context, fillPath);
|
||||
CGContextEOClip(context);
|
||||
CGColorRef shadowColor = [UIColor colorWithWhite:0 alpha:0.32].CGColor;
|
||||
CGContextSetShadowWithColor(context, CGSizeMake(0, 1.5), 3, shadowColor);
|
||||
CGContextBeginTransparencyLayer(context, NULL); {
|
||||
CGContextAddPath(context, fillPath);
|
||||
[[UIColor colorWithWhite:0.7 alpha:1.000] setFill];
|
||||
CGContextFillPath(context);
|
||||
} CGContextEndTransparencyLayer(context);
|
||||
} CGContextRestoreGState(context);
|
||||
|
||||
// stroke
|
||||
CGContextSaveGState(context); {
|
||||
CGContextAddPath(context, strokePath);
|
||||
[[UIColor colorWithWhite:0.6 alpha:1] setStroke];
|
||||
CGContextSetLineWidth(context, YYTextCGFloatFromPixel(1));
|
||||
CGContextStrokePath(context);
|
||||
} CGContextRestoreGState(context);
|
||||
|
||||
CFRelease(boxPath);
|
||||
CFRelease(fillPath);
|
||||
CFRelease(strokePath);
|
||||
|
||||
image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
});
|
||||
return image;
|
||||
}
|
||||
|
||||
|
||||
#undef kMultiple
|
||||
#undef kDiameter
|
||||
#undef kPadding
|
||||
#undef kSize
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@interface _YYTextMagnifierRanged : YYTextMagnifier {
|
||||
UIImageView *_contentView;
|
||||
UIImageView *_coverView;
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
@implementation _YYTextMagnifierRanged
|
||||
#define kMultiple 1.2
|
||||
#define kSize CGSizeMake(141, 60)
|
||||
#define kPadding YYTextCGFloatPixelHalf(6.0)
|
||||
#define kRadius 6.0
|
||||
#define kHeight 32.0
|
||||
#define kArrow 14.0
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
_contentView = [UIImageView new];
|
||||
_contentView.frame = CGRectMake(kPadding, kPadding, kSize.width - 2 * kPadding, kHeight);
|
||||
_contentView.layer.cornerRadius = kRadius;
|
||||
_contentView.clipsToBounds = YES;
|
||||
[self addSubview:_contentView];
|
||||
|
||||
_coverView = [UIImageView new];
|
||||
_coverView.frame = (CGRect){.origin = CGPointZero, .size = kSize};
|
||||
_coverView.image = [self.class coverImage];
|
||||
[self addSubview:_coverView];
|
||||
|
||||
self.layer.anchorPoint = CGPointMake(0.5, 1.2);
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [self initWithFrame:CGRectZero];
|
||||
self.frame = (CGRect){.size = [self sizeThatFits:CGSizeZero]};
|
||||
return self;
|
||||
}
|
||||
|
||||
- (YYTextMagnifierType)type {
|
||||
return YYTextMagnifierTypeRanged;
|
||||
}
|
||||
|
||||
- (CGSize)sizeThatFits:(CGSize)size {
|
||||
return kSize;
|
||||
}
|
||||
|
||||
- (void)setSnapshot:(UIImage *)snapshot {
|
||||
if (self.captureFadeAnimation) {
|
||||
[_contentView.layer removeAnimationForKey:@"contents"];
|
||||
CABasicAnimation *animation = [CABasicAnimation animation];
|
||||
animation.duration = kCaptureDisableFadeTime;
|
||||
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
|
||||
[_contentView.layer addAnimation:animation forKey:@"contents"];
|
||||
}
|
||||
_contentView.image = snapshot;
|
||||
}
|
||||
|
||||
- (UIImage *)snapshot {
|
||||
return _contentView.image;
|
||||
}
|
||||
|
||||
- (CGSize)snapshotSize {
|
||||
CGSize size;
|
||||
size.width = floor((kSize.width - 2 * kPadding) / kMultiple);
|
||||
size.height = floor(kHeight / kMultiple);
|
||||
return size;
|
||||
}
|
||||
|
||||
- (CGSize)fitSize {
|
||||
return [self sizeThatFits:CGSizeZero];
|
||||
}
|
||||
|
||||
+ (UIImage *)coverImage {
|
||||
static UIImage *image;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
CGSize size = kSize;
|
||||
CGRect rect = (CGRect) {.size = size, .origin = CGPointZero};
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
|
||||
CGPathRef boxPath = CGPathCreateWithRect(rect, NULL);
|
||||
|
||||
CGMutablePathRef path = CGPathCreateMutable();
|
||||
CGPathMoveToPoint(path, NULL, kPadding + kRadius, kPadding);
|
||||
CGPathAddLineToPoint(path, NULL, size.width - kPadding - kRadius, kPadding);
|
||||
CGPathAddQuadCurveToPoint(path, NULL, size.width - kPadding, kPadding, size.width - kPadding, kPadding + kRadius);
|
||||
CGPathAddLineToPoint(path, NULL, size.width - kPadding, kHeight);
|
||||
CGPathAddCurveToPoint(path, NULL, size.width - kPadding, kPadding + kHeight, size.width - kPadding - kRadius, kPadding + kHeight, size.width - kPadding - kRadius, kPadding + kHeight);
|
||||
CGPathAddLineToPoint(path, NULL, size.width / 2 + kArrow, kPadding + kHeight);
|
||||
CGPathAddLineToPoint(path, NULL, size.width / 2, kPadding + kHeight + kArrow);
|
||||
CGPathAddLineToPoint(path, NULL, size.width / 2 - kArrow, kPadding + kHeight);
|
||||
CGPathAddLineToPoint(path, NULL, kPadding + kRadius, kPadding + kHeight);
|
||||
CGPathAddQuadCurveToPoint(path, NULL, kPadding, kPadding + kHeight, kPadding, kHeight);
|
||||
CGPathAddLineToPoint(path, NULL, kPadding, kPadding + kRadius);
|
||||
CGPathAddQuadCurveToPoint(path, NULL, kPadding, kPadding, kPadding + kRadius, kPadding);
|
||||
CGPathCloseSubpath(path);
|
||||
|
||||
CGMutablePathRef arrowPath = CGPathCreateMutable();
|
||||
CGPathMoveToPoint(arrowPath, NULL, size.width / 2 - kArrow, YYTextCGFloatPixelFloor(kPadding) + kHeight);
|
||||
CGPathAddLineToPoint(arrowPath, NULL, size.width / 2 + kArrow, YYTextCGFloatPixelFloor(kPadding) + kHeight);
|
||||
CGPathAddLineToPoint(arrowPath, NULL, size.width / 2, kPadding + kHeight + kArrow);
|
||||
CGPathCloseSubpath(arrowPath);
|
||||
|
||||
// inner shadow
|
||||
CGContextSaveGState(context); {
|
||||
CGFloat blurRadius = 25;
|
||||
CGSize offset = CGSizeMake(0, 15);
|
||||
CGColorRef shadowColor = [UIColor colorWithWhite:0 alpha:0.16].CGColor;
|
||||
CGColorRef opaqueShadowColor = CGColorCreateCopyWithAlpha(shadowColor, 1.0);
|
||||
CGContextAddPath(context, path);
|
||||
CGContextClip(context);
|
||||
CGContextSetAlpha(context, CGColorGetAlpha(shadowColor));
|
||||
CGContextBeginTransparencyLayer(context, NULL); {
|
||||
CGContextSetShadowWithColor(context, offset, blurRadius, opaqueShadowColor);
|
||||
CGContextSetBlendMode(context, kCGBlendModeSourceOut);
|
||||
CGContextSetFillColorWithColor(context, opaqueShadowColor);
|
||||
CGContextAddPath(context, path);
|
||||
CGContextFillPath(context);
|
||||
} CGContextEndTransparencyLayer(context);
|
||||
CGColorRelease(opaqueShadowColor);
|
||||
} CGContextRestoreGState(context);
|
||||
|
||||
// outer shadow
|
||||
CGContextSaveGState(context); {
|
||||
CGContextAddPath(context, boxPath);
|
||||
CGContextAddPath(context, path);
|
||||
CGContextEOClip(context);
|
||||
CGColorRef shadowColor = [UIColor colorWithWhite:0 alpha:0.32].CGColor;
|
||||
CGContextSetShadowWithColor(context, CGSizeMake(0, 1.5), 3, shadowColor);
|
||||
CGContextBeginTransparencyLayer(context, NULL); {
|
||||
CGContextAddPath(context, path);
|
||||
[[UIColor colorWithWhite:0.7 alpha:1.000] setFill];
|
||||
CGContextFillPath(context);
|
||||
} CGContextEndTransparencyLayer(context);
|
||||
} CGContextRestoreGState(context);
|
||||
|
||||
// arrow
|
||||
CGContextSaveGState(context); {
|
||||
CGContextAddPath(context, arrowPath);
|
||||
[[UIColor colorWithWhite:1 alpha:0.95] set];
|
||||
CGContextFillPath(context);
|
||||
} CGContextRestoreGState(context);
|
||||
|
||||
// stroke
|
||||
CGContextSaveGState(context); {
|
||||
CGContextAddPath(context, path);
|
||||
[[UIColor colorWithWhite:0.6 alpha:1] setStroke];
|
||||
CGContextSetLineWidth(context, YYTextCGFloatFromPixel(1));
|
||||
CGContextStrokePath(context);
|
||||
} CGContextRestoreGState(context);
|
||||
|
||||
CFRelease(boxPath);
|
||||
CFRelease(path);
|
||||
CFRelease(arrowPath);
|
||||
|
||||
image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
});
|
||||
return image;
|
||||
}
|
||||
|
||||
#undef kMultiple
|
||||
#undef kSize
|
||||
#undef kPadding
|
||||
#undef kRadius
|
||||
#undef kHeight
|
||||
#undef kArrow
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation YYTextMagnifier
|
||||
|
||||
+ (id)magnifierWithType:(YYTextMagnifierType)type {
|
||||
switch (type) {
|
||||
case YYTextMagnifierTypeCaret :return [_YYTextMagnifierCaret new];
|
||||
case YYTextMagnifierTypeRanged :return [_YYTextMagnifierRanged new];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame {
|
||||
// class cluster
|
||||
if ([self isMemberOfClass:[YYTextMagnifier class]]) {
|
||||
@throw [NSException exceptionWithName:NSStringFromClass([self class]) reason:@"Attempting to instantiate an abstract class. Use a concrete subclass instead." userInfo:nil];
|
||||
return nil;
|
||||
}
|
||||
self = [super initWithFrame:frame];
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// YYTextSelectionView.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/2/25.
|
||||
// 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(<YYText/YYText.h>)
|
||||
#import <YYText/YYTextAttribute.h>
|
||||
#import <YYText/YYTextInput.h>
|
||||
#else
|
||||
#import "YYTextAttribute.h"
|
||||
#import "YYTextInput.h"
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
A single dot view. The frame should be foursquare.
|
||||
Change the background color for display.
|
||||
|
||||
@discussion Typically, you should not use this class directly.
|
||||
*/
|
||||
@interface YYSelectionGrabberDot : UIView
|
||||
/// Dont't access this property. It was used by `YYTextEffectWindow`.
|
||||
@property (nonatomic, strong) UIView *mirror;
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
A grabber (stick with a dot).
|
||||
|
||||
@discussion Typically, you should not use this class directly.
|
||||
*/
|
||||
@interface YYSelectionGrabber : UIView
|
||||
|
||||
@property (nonatomic, readonly) YYSelectionGrabberDot *dot; ///< the dot view
|
||||
@property (nonatomic) YYTextDirection dotDirection; ///< don't support composite direction
|
||||
@property (nullable, nonatomic, strong) UIColor *color; ///< tint color, default is nil
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
The selection view for text edit and select.
|
||||
|
||||
@discussion Typically, you should not use this class directly.
|
||||
*/
|
||||
@interface YYTextSelectionView : UIView
|
||||
|
||||
@property (nullable, nonatomic, weak) UIView *hostView; ///< the holder view
|
||||
@property (nullable, nonatomic, strong) UIColor *color; ///< the tint color
|
||||
@property (nonatomic, getter = isCaretBlinks) BOOL caretBlinks; ///< whether the caret is blinks
|
||||
@property (nonatomic, getter = isCaretVisible) BOOL caretVisible; ///< whether the caret is visible
|
||||
@property (nonatomic, getter = isVerticalForm) BOOL verticalForm; ///< weather the text view is vertical form
|
||||
|
||||
@property (nonatomic) CGRect caretRect; ///< caret rect (width==0 or height==0)
|
||||
@property (nullable, nonatomic, copy) NSArray<YYTextSelectionRect *> *selectionRects; ///< default is nil
|
||||
|
||||
@property (nonatomic, readonly) UIView *caretView;
|
||||
@property (nonatomic, readonly) YYSelectionGrabber *startGrabber;
|
||||
@property (nonatomic, readonly) YYSelectionGrabber *endGrabber;
|
||||
|
||||
- (BOOL)isGrabberContainsPoint:(CGPoint)point;
|
||||
- (BOOL)isStartGrabberContainsPoint:(CGPoint)point;
|
||||
- (BOOL)isEndGrabberContainsPoint:(CGPoint)point;
|
||||
- (BOOL)isCaretContainsPoint:(CGPoint)point;
|
||||
- (BOOL)isSelectionRectsContainsPoint:(CGPoint)point;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,329 @@
|
|||
//
|
||||
// YYTextSelectionView.m
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/2/25.
|
||||
// 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 "YYTextSelectionView.h"
|
||||
#import "YYTextUtilities.h"
|
||||
#import "YYTextWeakProxy.h"
|
||||
|
||||
#define kMarkAlpha 0.2
|
||||
#define kLineWidth 2.0
|
||||
#define kBlinkDuration 0.5
|
||||
#define kBlinkFadeDuration 0.2
|
||||
#define kBlinkFirstDelay 0.1
|
||||
#define kTouchTestExtend 14.0
|
||||
#define kTouchDotExtend 7.0
|
||||
|
||||
|
||||
@implementation YYSelectionGrabberDot
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (!self) return nil;
|
||||
self.userInteractionEnabled = NO;
|
||||
self.mirror = [UIView new];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
CGFloat length = MIN(self.bounds.size.width, self.bounds.size.height);
|
||||
self.layer.cornerRadius = length * 0.5;
|
||||
self.mirror.bounds = self.bounds;
|
||||
self.mirror.layer.cornerRadius = self.layer.cornerRadius;
|
||||
}
|
||||
|
||||
- (void)setBackgroundColor:(UIColor *)backgroundColor {
|
||||
[super setBackgroundColor:backgroundColor];
|
||||
_mirror.backgroundColor = backgroundColor;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation YYSelectionGrabber
|
||||
|
||||
- (instancetype) initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (!self) return nil;
|
||||
_dot = [[YYSelectionGrabberDot alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setDotDirection:(YYTextDirection)dotDirection {
|
||||
_dotDirection = dotDirection;
|
||||
[self addSubview:_dot];
|
||||
CGRect frame = _dot.frame;
|
||||
CGFloat ofs = 0.5;
|
||||
if (dotDirection == YYTextDirectionTop) {
|
||||
frame.origin.y = -frame.size.height + ofs;
|
||||
frame.origin.x = (self.bounds.size.width - frame.size.width) / 2;
|
||||
} else if (dotDirection == YYTextDirectionRight) {
|
||||
frame.origin.x = self.bounds.size.width - ofs;
|
||||
frame.origin.y = (self.bounds.size.height - frame.size.height) / 2;
|
||||
} else if (dotDirection == YYTextDirectionBottom) {
|
||||
frame.origin.y = self.bounds.size.height - ofs;
|
||||
frame.origin.x = (self.bounds.size.width - frame.size.width) / 2;
|
||||
} else if (dotDirection == YYTextDirectionLeft) {
|
||||
frame.origin.x = -frame.size.width + ofs;
|
||||
frame.origin.y = (self.bounds.size.height - frame.size.height) / 2;
|
||||
} else {
|
||||
[_dot removeFromSuperview];
|
||||
}
|
||||
_dot.frame = frame;
|
||||
}
|
||||
|
||||
- (void)setColor:(UIColor *)color {
|
||||
self.backgroundColor = color;
|
||||
_dot.backgroundColor = color;
|
||||
_color = color;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
[self setDotDirection:_dotDirection];
|
||||
}
|
||||
|
||||
- (CGRect)touchRect {
|
||||
CGRect rect = CGRectInset(self.frame, -kTouchTestExtend, -kTouchTestExtend);
|
||||
UIEdgeInsets insets = {0};
|
||||
if (_dotDirection == YYTextDirectionTop) {
|
||||
insets.top = -kTouchDotExtend;
|
||||
} else if (_dotDirection == YYTextDirectionRight) {
|
||||
insets.right = -kTouchDotExtend;
|
||||
} else if (_dotDirection == YYTextDirectionBottom) {
|
||||
insets.bottom = -kTouchDotExtend;
|
||||
} else if (_dotDirection == YYTextDirectionLeft) {
|
||||
insets.left = -kTouchDotExtend;
|
||||
}
|
||||
rect = UIEdgeInsetsInsetRect(rect, insets);
|
||||
return rect;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@interface YYTextSelectionView ()
|
||||
@property (nonatomic, strong) NSTimer *caretTimer;
|
||||
@property (nonatomic, strong) UIView *caretView;
|
||||
@property (nonatomic, strong) YYSelectionGrabber *startGrabber;
|
||||
@property (nonatomic, strong) YYSelectionGrabber *endGrabber;
|
||||
@property (nonatomic, strong) NSMutableArray *markViews;
|
||||
@end
|
||||
|
||||
@implementation YYTextSelectionView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (!self) return nil;
|
||||
|
||||
self.userInteractionEnabled = NO;
|
||||
self.clipsToBounds = NO;
|
||||
_markViews = [NSMutableArray array];
|
||||
_caretView = [UIView new];
|
||||
_caretView.hidden = YES;
|
||||
_startGrabber = [YYSelectionGrabber new];
|
||||
_startGrabber.dotDirection = YYTextDirectionTop;
|
||||
_startGrabber.hidden = YES;
|
||||
_endGrabber = [YYSelectionGrabber new];
|
||||
_endGrabber.dotDirection = YYTextDirectionBottom;
|
||||
_endGrabber.hidden = YES;
|
||||
|
||||
[self addSubview:_startGrabber];
|
||||
[self addSubview:_endGrabber];
|
||||
[self addSubview:_caretView];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_caretTimer invalidate];
|
||||
}
|
||||
|
||||
- (void)setColor:(UIColor *)color {
|
||||
_color = color;
|
||||
self.caretView.backgroundColor = color;
|
||||
self.startGrabber.color = color;
|
||||
self.endGrabber.color = color;
|
||||
[self.markViews enumerateObjectsUsingBlock: ^(UIView *v, NSUInteger idx, BOOL *stop) {
|
||||
v.backgroundColor = color;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setCaretBlinks:(BOOL)caretBlinks {
|
||||
if (_caretBlinks != caretBlinks) {
|
||||
_caretView.alpha = 1;
|
||||
[self.class cancelPreviousPerformRequestsWithTarget:self selector:@selector(_startBlinks) object:nil];
|
||||
if (caretBlinks) {
|
||||
[self performSelector:@selector(_startBlinks) withObject:nil afterDelay:kBlinkFirstDelay];
|
||||
} else {
|
||||
[_caretTimer invalidate];
|
||||
_caretTimer = nil;
|
||||
}
|
||||
_caretBlinks = caretBlinks;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_startBlinks {
|
||||
[_caretTimer invalidate];
|
||||
if (_caretVisible) {
|
||||
_caretTimer = [NSTimer timerWithTimeInterval:kBlinkDuration target:[YYTextWeakProxy proxyWithTarget:self] selector:@selector(_doBlink) userInfo:nil repeats:YES];
|
||||
[[NSRunLoop currentRunLoop] addTimer:_caretTimer forMode:NSDefaultRunLoopMode];
|
||||
} else {
|
||||
_caretView.alpha = 1;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_doBlink {
|
||||
[UIView animateWithDuration:kBlinkFadeDuration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations: ^{
|
||||
if (_caretView.alpha == 1) _caretView.alpha = 0;
|
||||
else _caretView.alpha = 1;
|
||||
} completion:NULL];
|
||||
}
|
||||
|
||||
- (void)setCaretVisible:(BOOL)caretVisible {
|
||||
_caretVisible = caretVisible;
|
||||
self.caretView.hidden = !caretVisible;
|
||||
_caretView.alpha = 1;
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_startBlinks) object:nil];
|
||||
if (_caretBlinks) {
|
||||
[self performSelector:@selector(_startBlinks) withObject:nil afterDelay:kBlinkFirstDelay];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setVerticalForm:(BOOL)verticalForm {
|
||||
if (_verticalForm != verticalForm) {
|
||||
_verticalForm = verticalForm;
|
||||
[self setCaretRect:_caretRect];
|
||||
self.startGrabber.dotDirection = verticalForm ? YYTextDirectionRight : YYTextDirectionTop;
|
||||
self.endGrabber.dotDirection = verticalForm ? YYTextDirectionLeft : YYTextDirectionBottom;
|
||||
}
|
||||
}
|
||||
|
||||
- (CGRect)_standardCaretRect:(CGRect)caretRect {
|
||||
caretRect = CGRectStandardize(caretRect);
|
||||
if (_verticalForm) {
|
||||
if (caretRect.size.height == 0) {
|
||||
caretRect.size.height = kLineWidth;
|
||||
caretRect.origin.y -= kLineWidth * 0.5;
|
||||
}
|
||||
if (caretRect.origin.y < 0) {
|
||||
caretRect.origin.y = 0;
|
||||
} else if (caretRect.origin.y + caretRect.size.height > self.bounds.size.height) {
|
||||
caretRect.origin.y = self.bounds.size.height - caretRect.size.height;
|
||||
}
|
||||
} else {
|
||||
if (caretRect.size.width == 0) {
|
||||
caretRect.size.width = kLineWidth;
|
||||
caretRect.origin.x -= kLineWidth * 0.5;
|
||||
}
|
||||
if (caretRect.origin.x < 0) {
|
||||
caretRect.origin.x = 0;
|
||||
} else if (caretRect.origin.x + caretRect.size.width > self.bounds.size.width) {
|
||||
caretRect.origin.x = self.bounds.size.width - caretRect.size.width;
|
||||
}
|
||||
}
|
||||
caretRect = YYTextCGRectPixelRound(caretRect);
|
||||
if (isnan(caretRect.origin.x) || isinf(caretRect.origin.x)) caretRect.origin.x = 0;
|
||||
if (isnan(caretRect.origin.y) || isinf(caretRect.origin.y)) caretRect.origin.y = 0;
|
||||
if (isnan(caretRect.size.width) || isinf(caretRect.size.width)) caretRect.size.width = 0;
|
||||
if (isnan(caretRect.size.height) || isinf(caretRect.size.height)) caretRect.size.height = 0;
|
||||
return caretRect;
|
||||
}
|
||||
|
||||
- (void)setCaretRect:(CGRect)caretRect {
|
||||
_caretRect = caretRect;
|
||||
self.caretView.frame = [self _standardCaretRect:caretRect];
|
||||
CGFloat minWidth = MIN(self.caretView.bounds.size.width, self.caretView.bounds.size.height);
|
||||
self.caretView.layer.cornerRadius = minWidth / 2;
|
||||
}
|
||||
|
||||
- (void)setSelectionRects:(NSArray *)selectionRects {
|
||||
_selectionRects = selectionRects.copy;
|
||||
[self.markViews enumerateObjectsUsingBlock: ^(UIView *v, NSUInteger idx, BOOL *stop) {
|
||||
[v removeFromSuperview];
|
||||
}];
|
||||
[self.markViews removeAllObjects];
|
||||
self.startGrabber.hidden = YES;
|
||||
self.endGrabber.hidden = YES;
|
||||
|
||||
[selectionRects enumerateObjectsUsingBlock: ^(YYTextSelectionRect *r, NSUInteger idx, BOOL *stop) {
|
||||
CGRect rect = r.rect;
|
||||
rect = CGRectStandardize(rect);
|
||||
rect = YYTextCGRectPixelRound(rect);
|
||||
if (r.containsStart || r.containsEnd) {
|
||||
rect = [self _standardCaretRect:rect];
|
||||
if (r.containsStart) {
|
||||
self.startGrabber.hidden = NO;
|
||||
self.startGrabber.frame = rect;
|
||||
}
|
||||
if (r.containsEnd) {
|
||||
self.endGrabber.hidden = NO;
|
||||
self.endGrabber.frame = rect;
|
||||
}
|
||||
} else {
|
||||
if (rect.size.width > 0 && rect.size.height > 0) {
|
||||
UIView *mark = [[UIView alloc] initWithFrame:rect];
|
||||
mark.backgroundColor = _color;
|
||||
mark.alpha = kMarkAlpha;
|
||||
[self insertSubview:mark atIndex:0];
|
||||
[self.markViews addObject:mark];
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (BOOL)isGrabberContainsPoint:(CGPoint)point {
|
||||
return [self isStartGrabberContainsPoint:point] || [self isEndGrabberContainsPoint:point];
|
||||
}
|
||||
|
||||
- (BOOL)isStartGrabberContainsPoint:(CGPoint)point {
|
||||
if (_startGrabber.hidden) return NO;
|
||||
CGRect startRect = [_startGrabber touchRect];
|
||||
CGRect endRect = [_endGrabber touchRect];
|
||||
if (CGRectIntersectsRect(startRect, endRect)) {
|
||||
CGFloat distStart = YYTextCGPointGetDistanceToPoint(point, YYTextCGRectGetCenter(startRect));
|
||||
CGFloat distEnd = YYTextCGPointGetDistanceToPoint(point, YYTextCGRectGetCenter(endRect));
|
||||
if (distEnd <= distStart) return NO;
|
||||
}
|
||||
return CGRectContainsPoint(startRect, point);
|
||||
}
|
||||
|
||||
- (BOOL)isEndGrabberContainsPoint:(CGPoint)point {
|
||||
if (_endGrabber.hidden) return NO;
|
||||
CGRect startRect = [_startGrabber touchRect];
|
||||
CGRect endRect = [_endGrabber touchRect];
|
||||
if (CGRectIntersectsRect(startRect, endRect)) {
|
||||
CGFloat distStart = YYTextCGPointGetDistanceToPoint(point, YYTextCGRectGetCenter(startRect));
|
||||
CGFloat distEnd = YYTextCGPointGetDistanceToPoint(point, YYTextCGRectGetCenter(endRect));
|
||||
if (distEnd > distStart) return NO;
|
||||
}
|
||||
return CGRectContainsPoint(endRect, point);
|
||||
}
|
||||
|
||||
- (BOOL)isCaretContainsPoint:(CGPoint)point {
|
||||
if (_caretVisible) {
|
||||
CGRect rect = CGRectInset(_caretRect, -kTouchTestExtend, -kTouchTestExtend);
|
||||
return CGRectContainsPoint(rect, point);
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isSelectionRectsContainsPoint:(CGPoint)point {
|
||||
if (_selectionRects.count == 0) return NO;
|
||||
for (YYTextSelectionRect *rect in _selectionRects) {
|
||||
if (CGRectContainsPoint(rect.rect, point)) return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// YYTextArchiver.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/3/16.
|
||||
// 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
|
||||
|
||||
/**
|
||||
A subclass of `NSKeyedArchiver` which implement `NSKeyedArchiverDelegate` protocol.
|
||||
|
||||
The archiver can encode the object which contains
|
||||
CGColor/CGImage/CTRunDelegateRef/.. (such as NSAttributedString).
|
||||
*/
|
||||
@interface YYTextArchiver : NSKeyedArchiver <NSKeyedArchiverDelegate>
|
||||
@end
|
||||
|
||||
/**
|
||||
A subclass of `NSKeyedUnarchiver` which implement `NSKeyedUnarchiverDelegate`
|
||||
protocol. The unarchiver can decode the data which is encoded by
|
||||
`YYTextArchiver` or `NSKeyedArchiver`.
|
||||
*/
|
||||
@interface YYTextUnarchiver : NSKeyedUnarchiver <NSKeyedUnarchiverDelegate>
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,252 @@
|
|||
//
|
||||
// YYTextArchiver.m
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/3/16.
|
||||
// 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 "YYTextArchiver.h"
|
||||
#import "YYTextRunDelegate.h"
|
||||
#import "YYTextRubyAnnotation.h"
|
||||
|
||||
/**
|
||||
When call CTRunDelegateGetTypeID() on some devices (runs iOS6), I got the error:
|
||||
"dyld: lazy symbol binding failed: Symbol not found: _CTRunDelegateGetTypeID"
|
||||
|
||||
Here's a workaround for this issue.
|
||||
*/
|
||||
static CFTypeID CTRunDelegateTypeID() {
|
||||
static CFTypeID typeID;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
/*
|
||||
if ((long)CTRunDelegateGetTypeID + 1 > 1) { //avoid compiler optimization
|
||||
typeID = CTRunDelegateGetTypeID();
|
||||
}
|
||||
*/
|
||||
YYTextRunDelegate *delegate = [YYTextRunDelegate new];
|
||||
CTRunDelegateRef ref = delegate.CTRunDelegate;
|
||||
typeID = CFGetTypeID(ref);
|
||||
CFRelease(ref);
|
||||
});
|
||||
return typeID;
|
||||
}
|
||||
|
||||
static CFTypeID CTRubyAnnotationTypeID() {
|
||||
static CFTypeID typeID;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
if ((long)CTRubyAnnotationGetTypeID + 1 > 1) { //avoid compiler optimization
|
||||
typeID = CTRunDelegateGetTypeID();
|
||||
} else {
|
||||
typeID = kCFNotFound;
|
||||
}
|
||||
});
|
||||
return typeID;
|
||||
}
|
||||
|
||||
/**
|
||||
A wrapper for CGColorRef. Used for Archive/Unarchive/Copy.
|
||||
*/
|
||||
@interface _YYCGColor : NSObject <NSCopying, NSCoding>
|
||||
@property (nonatomic, assign) CGColorRef CGColor;
|
||||
+ (instancetype)colorWithCGColor:(CGColorRef)CGColor;
|
||||
@end
|
||||
|
||||
@implementation _YYCGColor
|
||||
|
||||
+ (instancetype)colorWithCGColor:(CGColorRef)CGColor {
|
||||
_YYCGColor *color = [self new];
|
||||
color.CGColor = CGColor;
|
||||
return color;
|
||||
}
|
||||
|
||||
- (void)setCGColor:(CGColorRef)CGColor {
|
||||
if (_CGColor != CGColor) {
|
||||
if (CGColor) CGColor = (CGColorRef)CFRetain(CGColor);
|
||||
if (_CGColor) CFRelease(_CGColor);
|
||||
_CGColor = CGColor;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (_CGColor) CFRelease(_CGColor);
|
||||
_CGColor = NULL;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
_YYCGColor *color = [self.class new];
|
||||
color.CGColor = self.CGColor;
|
||||
return color;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
UIColor *color = [UIColor colorWithCGColor:_CGColor];
|
||||
[aCoder encodeObject:color forKey:@"color"];
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [self init];
|
||||
UIColor *color = [aDecoder decodeObjectForKey:@"color"];
|
||||
self.CGColor = color.CGColor;
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
A wrapper for CGImageRef. Used for Archive/Unarchive/Copy.
|
||||
*/
|
||||
@interface _YYCGImage : NSObject <NSCoding, NSCopying>
|
||||
@property (nonatomic, assign) CGImageRef CGImage;
|
||||
+ (instancetype)imageWithCGImage:(CGImageRef)CGImage;
|
||||
@end
|
||||
|
||||
@implementation _YYCGImage
|
||||
|
||||
+ (instancetype)imageWithCGImage:(CGImageRef)CGImage {
|
||||
_YYCGImage *image = [self new];
|
||||
image.CGImage = CGImage;
|
||||
return image;
|
||||
}
|
||||
|
||||
- (void)setCGImage:(CGImageRef)CGImage {
|
||||
if (_CGImage != CGImage) {
|
||||
if (CGImage) CGImage = (CGImageRef)CFRetain(CGImage);
|
||||
if (_CGImage) CFRelease(_CGImage);
|
||||
_CGImage = CGImage;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (_CGImage) CFRelease(_CGImage);
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
_YYCGImage *image = [self.class new];
|
||||
image.CGImage = self.CGImage;
|
||||
return image;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
UIImage *image = [UIImage imageWithCGImage:_CGImage];
|
||||
[aCoder encodeObject:image forKey:@"image"];
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [self init];
|
||||
UIImage *image = [aDecoder decodeObjectForKey:@"image"];
|
||||
self.CGImage = image.CGImage;
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation YYTextArchiver
|
||||
|
||||
+ (NSData *)archivedDataWithRootObject:(id)rootObject {
|
||||
if (!rootObject) return nil;
|
||||
NSMutableData *data = [NSMutableData data];
|
||||
YYTextArchiver *archiver = [[[self class] alloc] initForWritingWithMutableData:data];
|
||||
[archiver encodeRootObject:rootObject];
|
||||
[archiver finishEncoding];
|
||||
return data;
|
||||
}
|
||||
|
||||
+ (BOOL)archiveRootObject:(id)rootObject toFile:(NSString *)path {
|
||||
NSData *data = [self archivedDataWithRootObject:rootObject];
|
||||
if (!data) return NO;
|
||||
return [data writeToFile:path atomically:YES];
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
self.delegate = self;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initForWritingWithMutableData:(NSMutableData *)data {
|
||||
self = [super initForWritingWithMutableData:data];
|
||||
self.delegate = self;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)archiver:(NSKeyedArchiver *)archiver willEncodeObject:(id)object {
|
||||
CFTypeID typeID = CFGetTypeID((CFTypeRef)object);
|
||||
if (typeID == CTRunDelegateTypeID()) {
|
||||
CTRunDelegateRef runDelegate = (__bridge CFTypeRef)(object);
|
||||
id ref = CTRunDelegateGetRefCon(runDelegate);
|
||||
if (ref) return ref;
|
||||
} else if (typeID == CTRubyAnnotationTypeID()) {
|
||||
CTRubyAnnotationRef ctRuby = (__bridge CFTypeRef)(object);
|
||||
YYTextRubyAnnotation *ruby = [YYTextRubyAnnotation rubyWithCTRubyRef:ctRuby];
|
||||
if (ruby) return ruby;
|
||||
} else if (typeID == CGColorGetTypeID()) {
|
||||
return [_YYCGColor colorWithCGColor:(CGColorRef)object];
|
||||
} else if (typeID == CGImageGetTypeID()) {
|
||||
return [_YYCGImage imageWithCGImage:(CGImageRef)object];
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation YYTextUnarchiver
|
||||
|
||||
+ (id)unarchiveObjectWithData:(NSData *)data {
|
||||
if (data.length == 0) return nil;
|
||||
YYTextUnarchiver *unarchiver = [[self alloc] initForReadingWithData:data];
|
||||
return [unarchiver decodeObject];
|
||||
}
|
||||
|
||||
+ (id)unarchiveObjectWithFile:(NSString *)path {
|
||||
NSData *data = [NSData dataWithContentsOfFile:path];
|
||||
return [self unarchiveObjectWithData:data];
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
self.delegate = self;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initForReadingWithData:(NSData *)data {
|
||||
self = [super initForReadingWithData:data];
|
||||
self.delegate = self;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)unarchiver:(NSKeyedUnarchiver *)unarchiver didDecodeObject:(id) NS_RELEASES_ARGUMENT object NS_RETURNS_RETAINED {
|
||||
if ([object class] == [YYTextRunDelegate class]) {
|
||||
YYTextRunDelegate *runDelegate = object;
|
||||
CTRunDelegateRef ct = runDelegate.CTRunDelegate;
|
||||
id ctObj = (__bridge id)ct;
|
||||
if (ct) CFRelease(ct);
|
||||
return ctObj;
|
||||
} else if ([object class] == [YYTextRubyAnnotation class]) {
|
||||
YYTextRubyAnnotation *ruby = object;
|
||||
if ([UIDevice currentDevice].systemVersion.floatValue >= 8) {
|
||||
CTRubyAnnotationRef ct = ruby.CTRubyAnnotation;
|
||||
id ctObj = (__bridge id)(ct);
|
||||
if (ct) CFRelease(ct);
|
||||
return ctObj;
|
||||
} else {
|
||||
return object;
|
||||
}
|
||||
} else if ([object class] == [_YYCGColor class]) {
|
||||
_YYCGColor *color = object;
|
||||
return (id)color.CGColor;
|
||||
} else if ([object class] == [_YYCGImage class]) {
|
||||
_YYCGImage *image = object;
|
||||
return (id)image.CGImage;
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,347 @@
|
|||
//
|
||||
// YYTextAttribute.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// 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>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#pragma mark - Enum Define
|
||||
|
||||
/// The attribute type
|
||||
typedef NS_OPTIONS(NSInteger, YYTextAttributeType) {
|
||||
YYTextAttributeTypeNone = 0,
|
||||
YYTextAttributeTypeUIKit = 1 << 0, ///< UIKit attributes, such as UILabel/UITextField/drawInRect.
|
||||
YYTextAttributeTypeCoreText = 1 << 1, ///< CoreText attributes, used by CoreText.
|
||||
YYTextAttributeTypeYYText = 1 << 2, ///< YYText attributes, used by YYText.
|
||||
};
|
||||
|
||||
/// Get the attribute type from an attribute name.
|
||||
extern YYTextAttributeType YYTextAttributeGetType(NSString *attributeName);
|
||||
|
||||
/**
|
||||
Line style in YYText (similar to NSUnderlineStyle).
|
||||
*/
|
||||
typedef NS_OPTIONS (NSInteger, YYTextLineStyle) {
|
||||
// basic style (bitmask:0xFF)
|
||||
YYTextLineStyleNone = 0x00, ///< ( ) Do not draw a line (Default).
|
||||
YYTextLineStyleSingle = 0x01, ///< (──────) Draw a single line.
|
||||
YYTextLineStyleThick = 0x02, ///< (━━━━━━━) Draw a thick line.
|
||||
YYTextLineStyleDouble = 0x09, ///< (══════) Draw a double line.
|
||||
|
||||
// style pattern (bitmask:0xF00)
|
||||
YYTextLineStylePatternSolid = 0x000, ///< (────────) Draw a solid line (Default).
|
||||
YYTextLineStylePatternDot = 0x100, ///< (‑ ‑ ‑ ‑ ‑ ‑) Draw a line of dots.
|
||||
YYTextLineStylePatternDash = 0x200, ///< (— — — —) Draw a line of dashes.
|
||||
YYTextLineStylePatternDashDot = 0x300, ///< (— ‑ — ‑ — ‑) Draw a line of alternating dashes and dots.
|
||||
YYTextLineStylePatternDashDotDot = 0x400, ///< (— ‑ ‑ — ‑ ‑) Draw a line of alternating dashes and two dots.
|
||||
YYTextLineStylePatternCircleDot = 0x900, ///< (••••••••••••) Draw a line of small circle dots.
|
||||
};
|
||||
|
||||
/**
|
||||
Text vertical alignment.
|
||||
*/
|
||||
typedef NS_ENUM(NSInteger, YYTextVerticalAlignment) {
|
||||
YYTextVerticalAlignmentTop = 0, ///< Top alignment.
|
||||
YYTextVerticalAlignmentCenter = 1, ///< Center alignment.
|
||||
YYTextVerticalAlignmentBottom = 2, ///< Bottom alignment.
|
||||
};
|
||||
|
||||
/**
|
||||
The direction define in YYText.
|
||||
*/
|
||||
typedef NS_OPTIONS(NSUInteger, YYTextDirection) {
|
||||
YYTextDirectionNone = 0,
|
||||
YYTextDirectionTop = 1 << 0,
|
||||
YYTextDirectionRight = 1 << 1,
|
||||
YYTextDirectionBottom = 1 << 2,
|
||||
YYTextDirectionLeft = 1 << 3,
|
||||
};
|
||||
|
||||
/**
|
||||
The trunction type, tells the truncation engine which type of truncation is being requested.
|
||||
*/
|
||||
typedef NS_ENUM (NSUInteger, YYTextTruncationType) {
|
||||
/// No truncate.
|
||||
YYTextTruncationTypeNone = 0,
|
||||
|
||||
/// Truncate at the beginning of the line, leaving the end portion visible.
|
||||
YYTextTruncationTypeStart = 1,
|
||||
|
||||
/// Truncate at the end of the line, leaving the start portion visible.
|
||||
YYTextTruncationTypeEnd = 2,
|
||||
|
||||
/// Truncate in the middle of the line, leaving both the start and the end portions visible.
|
||||
YYTextTruncationTypeMiddle = 3,
|
||||
};
|
||||
|
||||
|
||||
|
||||
#pragma mark - Attribute Name Defined in YYText
|
||||
|
||||
/// The value of this attribute is a `YYTextBackedString` object.
|
||||
/// Use this attribute to store the original plain text if it is replaced by something else (such as attachment).
|
||||
UIKIT_EXTERN NSString *const YYTextBackedStringAttributeName;
|
||||
|
||||
/// The value of this attribute is a `YYTextBinding` object.
|
||||
/// Use this attribute to bind a range of text together, as if it was a single charactor.
|
||||
UIKIT_EXTERN NSString *const YYTextBindingAttributeName;
|
||||
|
||||
/// The value of this attribute is a `YYTextShadow` object.
|
||||
/// Use this attribute to add shadow to a range of text.
|
||||
/// Shadow will be drawn below text glyphs. Use YYTextShadow.subShadow to add multi-shadow.
|
||||
UIKIT_EXTERN NSString *const YYTextShadowAttributeName;
|
||||
|
||||
/// The value of this attribute is a `YYTextShadow` object.
|
||||
/// Use this attribute to add inner shadow to a range of text.
|
||||
/// Inner shadow will be drawn above text glyphs. Use YYTextShadow.subShadow to add multi-shadow.
|
||||
UIKIT_EXTERN NSString *const YYTextInnerShadowAttributeName;
|
||||
|
||||
/// The value of this attribute is a `YYTextDecoration` object.
|
||||
/// Use this attribute to add underline to a range of text.
|
||||
/// The underline will be drawn below text glyphs.
|
||||
UIKIT_EXTERN NSString *const YYTextUnderlineAttributeName;
|
||||
|
||||
/// The value of this attribute is a `YYTextDecoration` object.
|
||||
/// Use this attribute to add strikethrough (delete line) to a range of text.
|
||||
/// The strikethrough will be drawn above text glyphs.
|
||||
UIKIT_EXTERN NSString *const YYTextStrikethroughAttributeName;
|
||||
|
||||
/// The value of this attribute is a `YYTextBorder` object.
|
||||
/// Use this attribute to add cover border or cover color to a range of text.
|
||||
/// The border will be drawn above the text glyphs.
|
||||
UIKIT_EXTERN NSString *const YYTextBorderAttributeName;
|
||||
|
||||
/// The value of this attribute is a `YYTextBorder` object.
|
||||
/// Use this attribute to add background border or background color to a range of text.
|
||||
/// The border will be drawn below the text glyphs.
|
||||
UIKIT_EXTERN NSString *const YYTextBackgroundBorderAttributeName;
|
||||
|
||||
/// The value of this attribute is a `YYTextBorder` object.
|
||||
/// Use this attribute to add a code block border to one or more line of text.
|
||||
/// The border will be drawn below the text glyphs.
|
||||
UIKIT_EXTERN NSString *const YYTextBlockBorderAttributeName;
|
||||
|
||||
/// The value of this attribute is a `YYTextAttachment` object.
|
||||
/// Use this attribute to add attachment to text.
|
||||
/// It should be used in conjunction with a CTRunDelegate.
|
||||
UIKIT_EXTERN NSString *const YYTextAttachmentAttributeName;
|
||||
|
||||
/// The value of this attribute is a `YYTextHighlight` object.
|
||||
/// Use this attribute to add a touchable highlight state to a range of text.
|
||||
UIKIT_EXTERN NSString *const YYTextHighlightAttributeName;
|
||||
|
||||
/// The value of this attribute is a `NSValue` object stores CGAffineTransform.
|
||||
/// Use this attribute to add transform to each glyph in a range of text.
|
||||
UIKIT_EXTERN NSString *const YYTextGlyphTransformAttributeName;
|
||||
|
||||
|
||||
|
||||
#pragma mark - String Token Define
|
||||
|
||||
UIKIT_EXTERN NSString *const YYTextAttachmentToken; ///< Object replacement character (U+FFFC), used for text attachment.
|
||||
UIKIT_EXTERN NSString *const YYTextTruncationToken; ///< Horizontal ellipsis (U+2026), used for text truncation "…".
|
||||
|
||||
|
||||
|
||||
#pragma mark - Attribute Value Define
|
||||
|
||||
/**
|
||||
The tap/long press action callback defined in YYText.
|
||||
|
||||
@param containerView The text container view (such as YYLabel/YYTextView).
|
||||
@param text The whole text.
|
||||
@param range The text range in `text` (if no range, the range.location is NSNotFound).
|
||||
@param rect The text frame in `containerView` (if no data, the rect is CGRectNull).
|
||||
*/
|
||||
typedef void(^YYTextAction)(UIView *containerView, NSAttributedString *text, NSRange range, CGRect rect);
|
||||
|
||||
|
||||
/**
|
||||
YYTextBackedString objects are used by the NSAttributedString class cluster
|
||||
as the values for text backed string attributes (stored in the attributed
|
||||
string under the key named YYTextBackedStringAttributeName).
|
||||
|
||||
It may used for copy/paste plain text from attributed string.
|
||||
Example: If :) is replace by a custom emoji (such as😊), the backed string can be set to @":)".
|
||||
*/
|
||||
@interface YYTextBackedString : NSObject <NSCoding, NSCopying>
|
||||
+ (instancetype)stringWithString:(nullable NSString *)string;
|
||||
@property (nullable, nonatomic, copy) NSString *string; ///< backed string
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
YYTextBinding objects are used by the NSAttributedString class cluster
|
||||
as the values for shadow attributes (stored in the attributed string under
|
||||
the key named YYTextBindingAttributeName).
|
||||
|
||||
Add this to a range of text will make the specified characters 'binding together'.
|
||||
YYTextView will treat the range of text as a single character during text
|
||||
selection and edit.
|
||||
*/
|
||||
@interface YYTextBinding : NSObject <NSCoding, NSCopying>
|
||||
+ (instancetype)bindingWithDeleteConfirm:(BOOL)deleteConfirm;
|
||||
@property (nonatomic) BOOL deleteConfirm; ///< confirm the range when delete in YYTextView
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
YYTextShadow objects are used by the NSAttributedString class cluster
|
||||
as the values for shadow attributes (stored in the attributed string under
|
||||
the key named YYTextShadowAttributeName or YYTextInnerShadowAttributeName).
|
||||
|
||||
It's similar to `NSShadow`, but offers more options.
|
||||
*/
|
||||
@interface YYTextShadow : NSObject <NSCoding, NSCopying>
|
||||
+ (instancetype)shadowWithColor:(nullable UIColor *)color offset:(CGSize)offset radius:(CGFloat)radius;
|
||||
|
||||
@property (nullable, nonatomic, strong) UIColor *color; ///< shadow color
|
||||
@property (nonatomic) CGSize offset; ///< shadow offset
|
||||
@property (nonatomic) CGFloat radius; ///< shadow blur radius
|
||||
@property (nonatomic) CGBlendMode blendMode; ///< shadow blend mode
|
||||
@property (nullable, nonatomic, strong) YYTextShadow *subShadow; ///< a sub shadow which will be added above the parent shadow
|
||||
|
||||
+ (instancetype)shadowWithNSShadow:(NSShadow *)nsShadow; ///< convert NSShadow to YYTextShadow
|
||||
- (NSShadow *)nsShadow; ///< convert YYTextShadow to NSShadow
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
YYTextDecorationLine objects are used by the NSAttributedString class cluster
|
||||
as the values for decoration line attributes (stored in the attributed string under
|
||||
the key named YYTextUnderlineAttributeName or YYTextStrikethroughAttributeName).
|
||||
|
||||
When it's used as underline, the line is drawn below text glyphs;
|
||||
when it's used as strikethrough, the line is drawn above text glyphs.
|
||||
*/
|
||||
@interface YYTextDecoration : NSObject <NSCoding, NSCopying>
|
||||
+ (instancetype)decorationWithStyle:(YYTextLineStyle)style;
|
||||
+ (instancetype)decorationWithStyle:(YYTextLineStyle)style width:(nullable NSNumber *)width color:(nullable UIColor *)color;
|
||||
@property (nonatomic) YYTextLineStyle style; ///< line style
|
||||
@property (nullable, nonatomic, strong) NSNumber *width; ///< line width (nil means automatic width)
|
||||
@property (nullable, nonatomic, strong) UIColor *color; ///< line color (nil means automatic color)
|
||||
@property (nullable, nonatomic, strong) YYTextShadow *shadow; ///< line shadow
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
YYTextBorder objects are used by the NSAttributedString class cluster
|
||||
as the values for border attributes (stored in the attributed string under
|
||||
the key named YYTextBorderAttributeName or YYTextBackgroundBorderAttributeName).
|
||||
|
||||
It can be used to draw a border around a range of text, or draw a background
|
||||
to a range of text.
|
||||
|
||||
Example:
|
||||
╭──────╮
|
||||
│ Text │
|
||||
╰──────╯
|
||||
*/
|
||||
@interface YYTextBorder : NSObject <NSCoding, NSCopying>
|
||||
+ (instancetype)borderWithLineStyle:(YYTextLineStyle)lineStyle lineWidth:(CGFloat)width strokeColor:(nullable UIColor *)color;
|
||||
+ (instancetype)borderWithFillColor:(nullable UIColor *)color cornerRadius:(CGFloat)cornerRadius;
|
||||
@property (nonatomic) YYTextLineStyle lineStyle; ///< border line style
|
||||
@property (nonatomic) CGFloat strokeWidth; ///< border line width
|
||||
@property (nullable, nonatomic, strong) UIColor *strokeColor; ///< border line color
|
||||
@property (nonatomic) CGLineJoin lineJoin; ///< border line join
|
||||
@property (nonatomic) UIEdgeInsets insets; ///< border insets for text bounds
|
||||
@property (nonatomic) CGFloat cornerRadius; ///< border corder radius
|
||||
@property (nullable, nonatomic, strong) YYTextShadow *shadow; ///< border shadow
|
||||
@property (nullable, nonatomic, strong) UIColor *fillColor; ///< inner fill color
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
YYTextAttachment objects are used by the NSAttributedString class cluster
|
||||
as the values for attachment attributes (stored in the attributed string under
|
||||
the key named YYTextAttachmentAttributeName).
|
||||
|
||||
When display an attributed string which contains `YYTextAttachment` object,
|
||||
the content will be placed in text metric. If the content is `UIImage`,
|
||||
then it will be drawn to CGContext; if the content is `UIView` or `CALayer`,
|
||||
then it will be added to the text container's view or layer.
|
||||
*/
|
||||
@interface YYTextAttachment : NSObject<NSCoding, NSCopying>
|
||||
+ (instancetype)attachmentWithContent:(nullable id)content;
|
||||
@property (nullable, nonatomic, strong) id content; ///< Supported type: UIImage, UIView, CALayer
|
||||
@property (nonatomic) UIViewContentMode contentMode; ///< Content display mode.
|
||||
@property (nonatomic) UIEdgeInsets contentInsets; ///< The insets when drawing content.
|
||||
@property (nullable, nonatomic, strong) NSDictionary *userInfo; ///< The user information dictionary.
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
YYTextHighlight objects are used by the NSAttributedString class cluster
|
||||
as the values for touchable highlight attributes (stored in the attributed string
|
||||
under the key named YYTextHighlightAttributeName).
|
||||
|
||||
When display an attributed string in `YYLabel` or `YYTextView`, the range of
|
||||
highlight text can be toucheds down by users. If a range of text is turned into
|
||||
highlighted state, the `attributes` in `YYTextHighlight` will be used to modify
|
||||
(set or remove) the original attributes in the range for display.
|
||||
*/
|
||||
@interface YYTextHighlight : NSObject <NSCoding, NSCopying>
|
||||
|
||||
/**
|
||||
Attributes that you can apply to text in an attributed string when highlight.
|
||||
Key: Same as CoreText/YYText Attribute Name.
|
||||
Value: Modify attribute value when highlight (NSNull for remove attribute).
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) NSDictionary<NSString *, id> *attributes;
|
||||
|
||||
/**
|
||||
Creates a highlight object with specified attributes.
|
||||
|
||||
@param attributes The attributes which will replace original attributes when highlight,
|
||||
If the value is NSNull, it will removed when highlight.
|
||||
*/
|
||||
+ (instancetype)highlightWithAttributes:(nullable NSDictionary<NSString *, id> *)attributes;
|
||||
|
||||
/**
|
||||
Convenience methods to create a default highlight with the specifeid background color.
|
||||
|
||||
@param color The background border color.
|
||||
*/
|
||||
+ (instancetype)highlightWithBackgroundColor:(nullable UIColor *)color;
|
||||
|
||||
// Convenience methods below to set the `attributes`.
|
||||
- (void)setFont:(nullable UIFont *)font;
|
||||
- (void)setColor:(nullable UIColor *)color;
|
||||
- (void)setStrokeWidth:(nullable NSNumber *)width;
|
||||
- (void)setStrokeColor:(nullable UIColor *)color;
|
||||
- (void)setShadow:(nullable YYTextShadow *)shadow;
|
||||
- (void)setInnerShadow:(nullable YYTextShadow *)shadow;
|
||||
- (void)setUnderline:(nullable YYTextDecoration *)underline;
|
||||
- (void)setStrikethrough:(nullable YYTextDecoration *)strikethrough;
|
||||
- (void)setBackgroundBorder:(nullable YYTextBorder *)border;
|
||||
- (void)setBorder:(nullable YYTextBorder *)border;
|
||||
- (void)setAttachment:(nullable YYTextAttachment *)attachment;
|
||||
|
||||
/**
|
||||
The user information dictionary, default is nil.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) NSDictionary *userInfo;
|
||||
|
||||
/**
|
||||
Tap action when user tap the highlight, default is nil.
|
||||
If the value is nil, YYTextView or YYLabel will ask it's delegate to handle the tap action.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) YYTextAction tapAction;
|
||||
|
||||
/**
|
||||
Long press action when user long press the highlight, default is nil.
|
||||
If the value is nil, YYTextView or YYLabel will ask it's delegate to handle the long press action.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) YYTextAction longPressAction;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,524 @@
|
|||
//
|
||||
// YYTextAttribute.m
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// 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 "YYTextAttribute.h"
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <CoreText/CoreText.h>
|
||||
#import "NSAttributedString+YYText.h"
|
||||
#import "YYTextArchiver.h"
|
||||
|
||||
|
||||
static double _YYDeviceSystemVersion() {
|
||||
static double version;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
version = [UIDevice currentDevice].systemVersion.doubleValue;
|
||||
});
|
||||
return version;
|
||||
}
|
||||
|
||||
|
||||
NSString *const YYTextBackedStringAttributeName = @"YYTextBackedString";
|
||||
NSString *const YYTextBindingAttributeName = @"YYTextBinding";
|
||||
NSString *const YYTextShadowAttributeName = @"YYTextShadow";
|
||||
NSString *const YYTextInnerShadowAttributeName = @"YYTextInnerShadow";
|
||||
NSString *const YYTextUnderlineAttributeName = @"YYTextUnderline";
|
||||
NSString *const YYTextStrikethroughAttributeName = @"YYTextStrikethrough";
|
||||
NSString *const YYTextBorderAttributeName = @"YYTextBorder";
|
||||
NSString *const YYTextBackgroundBorderAttributeName = @"YYTextBackgroundBorder";
|
||||
NSString *const YYTextBlockBorderAttributeName = @"YYTextBlockBorder";
|
||||
NSString *const YYTextAttachmentAttributeName = @"YYTextAttachment";
|
||||
NSString *const YYTextHighlightAttributeName = @"YYTextHighlight";
|
||||
NSString *const YYTextGlyphTransformAttributeName = @"YYTextGlyphTransform";
|
||||
|
||||
NSString *const YYTextAttachmentToken = @"\uFFFC";
|
||||
NSString *const YYTextTruncationToken = @"\u2026";
|
||||
|
||||
|
||||
YYTextAttributeType YYTextAttributeGetType(NSString *name){
|
||||
if (name.length == 0) return YYTextAttributeTypeNone;
|
||||
|
||||
static NSMutableDictionary *dic;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
dic = [NSMutableDictionary new];
|
||||
NSNumber *All = @(YYTextAttributeTypeUIKit | YYTextAttributeTypeCoreText | YYTextAttributeTypeYYText);
|
||||
NSNumber *CoreText_YYText = @(YYTextAttributeTypeCoreText | YYTextAttributeTypeYYText);
|
||||
NSNumber *UIKit_YYText = @(YYTextAttributeTypeUIKit | YYTextAttributeTypeYYText);
|
||||
NSNumber *UIKit_CoreText = @(YYTextAttributeTypeUIKit | YYTextAttributeTypeCoreText);
|
||||
NSNumber *UIKit = @(YYTextAttributeTypeUIKit);
|
||||
NSNumber *CoreText = @(YYTextAttributeTypeCoreText);
|
||||
NSNumber *YYText = @(YYTextAttributeTypeYYText);
|
||||
|
||||
dic[NSFontAttributeName] = All;
|
||||
dic[NSKernAttributeName] = All;
|
||||
dic[NSForegroundColorAttributeName] = UIKit;
|
||||
dic[(id)kCTForegroundColorAttributeName] = CoreText;
|
||||
dic[(id)kCTForegroundColorFromContextAttributeName] = CoreText;
|
||||
dic[NSBackgroundColorAttributeName] = UIKit;
|
||||
dic[NSStrokeWidthAttributeName] = All;
|
||||
dic[NSStrokeColorAttributeName] = UIKit;
|
||||
dic[(id)kCTStrokeColorAttributeName] = CoreText_YYText;
|
||||
dic[NSShadowAttributeName] = UIKit_YYText;
|
||||
dic[NSStrikethroughStyleAttributeName] = UIKit;
|
||||
dic[NSUnderlineStyleAttributeName] = UIKit_CoreText;
|
||||
dic[(id)kCTUnderlineColorAttributeName] = CoreText;
|
||||
dic[NSLigatureAttributeName] = All;
|
||||
dic[(id)kCTSuperscriptAttributeName] = UIKit; //it's a CoreText attrubite, but only supported by UIKit...
|
||||
dic[NSVerticalGlyphFormAttributeName] = All;
|
||||
dic[(id)kCTGlyphInfoAttributeName] = CoreText_YYText;
|
||||
dic[(id)kCTCharacterShapeAttributeName] = CoreText_YYText;
|
||||
dic[(id)kCTRunDelegateAttributeName] = CoreText_YYText;
|
||||
dic[(id)kCTBaselineClassAttributeName] = CoreText_YYText;
|
||||
dic[(id)kCTBaselineInfoAttributeName] = CoreText_YYText;
|
||||
dic[(id)kCTBaselineReferenceInfoAttributeName] = CoreText_YYText;
|
||||
dic[(id)kCTWritingDirectionAttributeName] = CoreText_YYText;
|
||||
dic[NSParagraphStyleAttributeName] = All;
|
||||
|
||||
if (_YYDeviceSystemVersion() >= 7) {
|
||||
dic[NSStrikethroughColorAttributeName] = UIKit;
|
||||
dic[NSUnderlineColorAttributeName] = UIKit;
|
||||
dic[NSTextEffectAttributeName] = UIKit;
|
||||
dic[NSObliquenessAttributeName] = UIKit;
|
||||
dic[NSExpansionAttributeName] = UIKit;
|
||||
dic[(id)kCTLanguageAttributeName] = CoreText_YYText;
|
||||
dic[NSBaselineOffsetAttributeName] = UIKit;
|
||||
dic[NSWritingDirectionAttributeName] = All;
|
||||
dic[NSAttachmentAttributeName] = UIKit;
|
||||
dic[NSLinkAttributeName] = UIKit;
|
||||
}
|
||||
if (_YYDeviceSystemVersion() >= 8) {
|
||||
dic[(id)kCTRubyAnnotationAttributeName] = CoreText;
|
||||
}
|
||||
|
||||
dic[YYTextBackedStringAttributeName] = YYText;
|
||||
dic[YYTextBindingAttributeName] = YYText;
|
||||
dic[YYTextShadowAttributeName] = YYText;
|
||||
dic[YYTextInnerShadowAttributeName] = YYText;
|
||||
dic[YYTextUnderlineAttributeName] = YYText;
|
||||
dic[YYTextStrikethroughAttributeName] = YYText;
|
||||
dic[YYTextBorderAttributeName] = YYText;
|
||||
dic[YYTextBackgroundBorderAttributeName] = YYText;
|
||||
dic[YYTextBlockBorderAttributeName] = YYText;
|
||||
dic[YYTextAttachmentAttributeName] = YYText;
|
||||
dic[YYTextHighlightAttributeName] = YYText;
|
||||
dic[YYTextGlyphTransformAttributeName] = YYText;
|
||||
});
|
||||
NSNumber *num = dic[name];
|
||||
if (num) return num.integerValue;
|
||||
return YYTextAttributeTypeNone;
|
||||
}
|
||||
|
||||
|
||||
@implementation YYTextBackedString
|
||||
|
||||
+ (instancetype)stringWithString:(NSString *)string {
|
||||
YYTextBackedString *one = [self new];
|
||||
one.string = string;
|
||||
return one;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[aCoder encodeObject:self.string forKey:@"string"];
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super init];
|
||||
_string = [aDecoder decodeObjectForKey:@"string"];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
typeof(self) one = [self.class new];
|
||||
one.string = self.string;
|
||||
return one;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation YYTextBinding
|
||||
|
||||
+ (instancetype)bindingWithDeleteConfirm:(BOOL)deleteConfirm {
|
||||
YYTextBinding *one = [self new];
|
||||
one.deleteConfirm = deleteConfirm;
|
||||
return one;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[aCoder encodeObject:@(self.deleteConfirm) forKey:@"deleteConfirm"];
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super init];
|
||||
_deleteConfirm = ((NSNumber *)[aDecoder decodeObjectForKey:@"deleteConfirm"]).boolValue;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
typeof(self) one = [self.class new];
|
||||
one.deleteConfirm = self.deleteConfirm;
|
||||
return one;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation YYTextShadow
|
||||
|
||||
+ (instancetype)shadowWithColor:(UIColor *)color offset:(CGSize)offset radius:(CGFloat)radius {
|
||||
YYTextShadow *one = [self new];
|
||||
one.color = color;
|
||||
one.offset = offset;
|
||||
one.radius = radius;
|
||||
return one;
|
||||
}
|
||||
|
||||
+ (instancetype)shadowWithNSShadow:(NSShadow *)nsShadow {
|
||||
if (!nsShadow) return nil;
|
||||
YYTextShadow *shadow = [self new];
|
||||
shadow.offset = nsShadow.shadowOffset;
|
||||
shadow.radius = nsShadow.shadowBlurRadius;
|
||||
id color = nsShadow.shadowColor;
|
||||
if (color) {
|
||||
if (CGColorGetTypeID() == CFGetTypeID((__bridge CFTypeRef)(color))) {
|
||||
color = [UIColor colorWithCGColor:(__bridge CGColorRef)(color)];
|
||||
}
|
||||
if ([color isKindOfClass:[UIColor class]]) {
|
||||
shadow.color = color;
|
||||
}
|
||||
}
|
||||
return shadow;
|
||||
}
|
||||
|
||||
- (NSShadow *)nsShadow {
|
||||
NSShadow *shadow = [NSShadow new];
|
||||
shadow.shadowOffset = self.offset;
|
||||
shadow.shadowBlurRadius = self.radius;
|
||||
shadow.shadowColor = self.color;
|
||||
return shadow;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[aCoder encodeObject:self.color forKey:@"color"];
|
||||
[aCoder encodeObject:@(self.radius) forKey:@"radius"];
|
||||
[aCoder encodeObject:[NSValue valueWithCGSize:self.offset] forKey:@"offset"];
|
||||
[aCoder encodeObject:self.subShadow forKey:@"subShadow"];
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super init];
|
||||
_color = [aDecoder decodeObjectForKey:@"color"];
|
||||
_radius = ((NSNumber *)[aDecoder decodeObjectForKey:@"radius"]).floatValue;
|
||||
_offset = ((NSValue *)[aDecoder decodeObjectForKey:@"offset"]).CGSizeValue;
|
||||
_subShadow = [aDecoder decodeObjectForKey:@"subShadow"];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
typeof(self) one = [self.class new];
|
||||
one.color = self.color;
|
||||
one.radius = self.radius;
|
||||
one.offset = self.offset;
|
||||
one.subShadow = self.subShadow.copy;
|
||||
return one;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation YYTextDecoration
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
_style = YYTextLineStyleSingle;
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype)decorationWithStyle:(YYTextLineStyle)style {
|
||||
YYTextDecoration *one = [self new];
|
||||
one.style = style;
|
||||
return one;
|
||||
}
|
||||
+ (instancetype)decorationWithStyle:(YYTextLineStyle)style width:(NSNumber *)width color:(UIColor *)color {
|
||||
YYTextDecoration *one = [self new];
|
||||
one.style = style;
|
||||
one.width = width;
|
||||
one.color = color;
|
||||
return one;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[aCoder encodeObject:@(self.style) forKey:@"style"];
|
||||
[aCoder encodeObject:self.width forKey:@"width"];
|
||||
[aCoder encodeObject:self.color forKey:@"color"];
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super init];
|
||||
self.style = ((NSNumber *)[aDecoder decodeObjectForKey:@"style"]).unsignedIntegerValue;
|
||||
self.width = [aDecoder decodeObjectForKey:@"width"];
|
||||
self.color = [aDecoder decodeObjectForKey:@"color"];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
typeof(self) one = [self.class new];
|
||||
one.style = self.style;
|
||||
one.width = self.width;
|
||||
one.color = self.color;
|
||||
return one;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation YYTextBorder
|
||||
|
||||
+ (instancetype)borderWithLineStyle:(YYTextLineStyle)lineStyle lineWidth:(CGFloat)width strokeColor:(UIColor *)color {
|
||||
YYTextBorder *one = [self new];
|
||||
one.lineStyle = lineStyle;
|
||||
one.strokeWidth = width;
|
||||
one.strokeColor = color;
|
||||
return one;
|
||||
}
|
||||
|
||||
+ (instancetype)borderWithFillColor:(UIColor *)color cornerRadius:(CGFloat)cornerRadius {
|
||||
YYTextBorder *one = [self new];
|
||||
one.fillColor = color;
|
||||
one.cornerRadius = cornerRadius;
|
||||
one.insets = UIEdgeInsetsMake(-2, 0, 0, -2);
|
||||
return one;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
self.lineStyle = YYTextLineStyleSingle;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[aCoder encodeObject:@(self.lineStyle) forKey:@"lineStyle"];
|
||||
[aCoder encodeObject:@(self.strokeWidth) forKey:@"strokeWidth"];
|
||||
[aCoder encodeObject:self.strokeColor forKey:@"strokeColor"];
|
||||
[aCoder encodeObject:@(self.lineJoin) forKey:@"lineJoin"];
|
||||
[aCoder encodeObject:[NSValue valueWithUIEdgeInsets:self.insets] forKey:@"insets"];
|
||||
[aCoder encodeObject:@(self.cornerRadius) forKey:@"cornerRadius"];
|
||||
[aCoder encodeObject:self.shadow forKey:@"shadow"];
|
||||
[aCoder encodeObject:self.fillColor forKey:@"fillColor"];
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super init];
|
||||
_lineStyle = ((NSNumber *)[aDecoder decodeObjectForKey:@"lineStyle"]).unsignedIntegerValue;
|
||||
_strokeWidth = ((NSNumber *)[aDecoder decodeObjectForKey:@"strokeWidth"]).doubleValue;
|
||||
_strokeColor = [aDecoder decodeObjectForKey:@"strokeColor"];
|
||||
_lineJoin = (CGLineJoin)((NSNumber *)[aDecoder decodeObjectForKey:@"join"]).unsignedIntegerValue;
|
||||
_insets = ((NSValue *)[aDecoder decodeObjectForKey:@"insets"]).UIEdgeInsetsValue;
|
||||
_cornerRadius = ((NSNumber *)[aDecoder decodeObjectForKey:@"cornerRadius"]).doubleValue;
|
||||
_shadow = [aDecoder decodeObjectForKey:@"shadow"];
|
||||
_fillColor = [aDecoder decodeObjectForKey:@"fillColor"];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
typeof(self) one = [self.class new];
|
||||
one.lineStyle = self.lineStyle;
|
||||
one.strokeWidth = self.strokeWidth;
|
||||
one.strokeColor = self.strokeColor;
|
||||
one.lineJoin = self.lineJoin;
|
||||
one.insets = self.insets;
|
||||
one.cornerRadius = self.cornerRadius;
|
||||
one.shadow = self.shadow.copy;
|
||||
one.fillColor = self.fillColor;
|
||||
return one;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation YYTextAttachment
|
||||
|
||||
+ (instancetype)attachmentWithContent:(id)content {
|
||||
YYTextAttachment *one = [self new];
|
||||
one.content = content;
|
||||
return one;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[aCoder encodeObject:self.content forKey:@"content"];
|
||||
[aCoder encodeObject:[NSValue valueWithUIEdgeInsets:self.contentInsets] forKey:@"contentInsets"];
|
||||
[aCoder encodeObject:self.userInfo forKey:@"userInfo"];
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super init];
|
||||
_content = [aDecoder decodeObjectForKey:@"content"];
|
||||
_contentInsets = ((NSValue *)[aDecoder decodeObjectForKey:@"contentInsets"]).UIEdgeInsetsValue;
|
||||
_userInfo = [aDecoder decodeObjectForKey:@"userInfo"];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
typeof(self) one = [self.class new];
|
||||
if ([self.content respondsToSelector:@selector(copy)]) {
|
||||
one.content = [self.content copy];
|
||||
} else {
|
||||
one.content = self.content;
|
||||
}
|
||||
one.contentInsets = self.contentInsets;
|
||||
one.userInfo = self.userInfo.copy;
|
||||
return one;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation YYTextHighlight
|
||||
|
||||
+ (instancetype)highlightWithAttributes:(NSDictionary *)attributes {
|
||||
YYTextHighlight *one = [self new];
|
||||
one.attributes = attributes;
|
||||
return one;
|
||||
}
|
||||
|
||||
+ (instancetype)highlightWithBackgroundColor:(UIColor *)color {
|
||||
YYTextBorder *highlightBorder = [YYTextBorder new];
|
||||
highlightBorder.insets = UIEdgeInsetsMake(-2, -1, -2, -1);
|
||||
highlightBorder.cornerRadius = 3;
|
||||
highlightBorder.fillColor = color;
|
||||
|
||||
YYTextHighlight *one = [self new];
|
||||
[one setBackgroundBorder:highlightBorder];
|
||||
return one;
|
||||
}
|
||||
|
||||
- (void)setAttributes:(NSDictionary *)attributes {
|
||||
_attributes = attributes.mutableCopy;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
NSData *data = nil;
|
||||
@try {
|
||||
data = [YYTextArchiver archivedDataWithRootObject:self.attributes];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSLog(@"%@",exception);
|
||||
}
|
||||
[aCoder encodeObject:data forKey:@"attributes"];
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super init];
|
||||
NSData *data = [aDecoder decodeObjectForKey:@"attributes"];
|
||||
@try {
|
||||
_attributes = [YYTextUnarchiver unarchiveObjectWithData:data];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSLog(@"%@",exception);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
typeof(self) one = [self.class new];
|
||||
one.attributes = self.attributes.mutableCopy;
|
||||
return one;
|
||||
}
|
||||
|
||||
- (void)_makeMutableAttributes {
|
||||
if (!_attributes) {
|
||||
_attributes = [NSMutableDictionary new];
|
||||
} else if (![_attributes isKindOfClass:[NSMutableDictionary class]]) {
|
||||
_attributes = _attributes.mutableCopy;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setFont:(UIFont *)font {
|
||||
[self _makeMutableAttributes];
|
||||
if (font == (id)[NSNull null] || font == nil) {
|
||||
((NSMutableDictionary *)_attributes)[(id)kCTFontAttributeName] = [NSNull null];
|
||||
} else {
|
||||
CTFontRef ctFont = CTFontCreateWithName((__bridge CFStringRef)font.fontName, font.pointSize, NULL);
|
||||
if (ctFont) {
|
||||
((NSMutableDictionary *)_attributes)[(id)kCTFontAttributeName] = (__bridge id)(ctFont);
|
||||
CFRelease(ctFont);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setColor:(UIColor *)color {
|
||||
[self _makeMutableAttributes];
|
||||
if (color == (id)[NSNull null] || color == nil) {
|
||||
((NSMutableDictionary *)_attributes)[(id)kCTForegroundColorAttributeName] = [NSNull null];
|
||||
((NSMutableDictionary *)_attributes)[NSForegroundColorAttributeName] = [NSNull null];
|
||||
} else {
|
||||
((NSMutableDictionary *)_attributes)[(id)kCTForegroundColorAttributeName] = (__bridge id)(color.CGColor);
|
||||
((NSMutableDictionary *)_attributes)[NSForegroundColorAttributeName] = color;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setStrokeWidth:(NSNumber *)width {
|
||||
[self _makeMutableAttributes];
|
||||
if (width == (id)[NSNull null] || width == nil) {
|
||||
((NSMutableDictionary *)_attributes)[(id)kCTStrokeWidthAttributeName] = [NSNull null];
|
||||
} else {
|
||||
((NSMutableDictionary *)_attributes)[(id)kCTStrokeWidthAttributeName] = width;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setStrokeColor:(UIColor *)color {
|
||||
[self _makeMutableAttributes];
|
||||
if (color == (id)[NSNull null] || color == nil) {
|
||||
((NSMutableDictionary *)_attributes)[(id)kCTStrokeColorAttributeName] = [NSNull null];
|
||||
((NSMutableDictionary *)_attributes)[NSStrokeColorAttributeName] = [NSNull null];
|
||||
} else {
|
||||
((NSMutableDictionary *)_attributes)[(id)kCTStrokeColorAttributeName] = (__bridge id)(color.CGColor);
|
||||
((NSMutableDictionary *)_attributes)[NSStrokeColorAttributeName] = color;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setTextAttribute:(NSString *)attribute value:(id)value {
|
||||
[self _makeMutableAttributes];
|
||||
if (value == nil) value = [NSNull null];
|
||||
((NSMutableDictionary *)_attributes)[attribute] = value;
|
||||
}
|
||||
|
||||
- (void)setShadow:(YYTextShadow *)shadow {
|
||||
[self setTextAttribute:YYTextShadowAttributeName value:shadow];
|
||||
}
|
||||
|
||||
- (void)setInnerShadow:(YYTextShadow *)shadow {
|
||||
[self setTextAttribute:YYTextInnerShadowAttributeName value:shadow];
|
||||
}
|
||||
|
||||
- (void)setUnderline:(YYTextDecoration *)underline {
|
||||
[self setTextAttribute:YYTextUnderlineAttributeName value:underline];
|
||||
}
|
||||
|
||||
- (void)setStrikethrough:(YYTextDecoration *)strikethrough {
|
||||
[self setTextAttribute:YYTextStrikethroughAttributeName value:strikethrough];
|
||||
}
|
||||
|
||||
- (void)setBackgroundBorder:(YYTextBorder *)border {
|
||||
[self setTextAttribute:YYTextBackgroundBorderAttributeName value:border];
|
||||
}
|
||||
|
||||
- (void)setBorder:(YYTextBorder *)border {
|
||||
[self setTextAttribute:YYTextBorderAttributeName value:border];
|
||||
}
|
||||
|
||||
- (void)setAttachment:(YYTextAttachment *)attachment {
|
||||
[self setTextAttribute:YYTextAttachmentAttributeName value:attachment];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
//
|
||||
// YYTextParser.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/3/6.
|
||||
// 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
|
||||
|
||||
/**
|
||||
The YYTextParser protocol declares the required method for YYTextView and YYLabel
|
||||
to modify the text during editing.
|
||||
|
||||
You can implement this protocol to add code highlighting or emoticon replacement for
|
||||
YYTextView and YYLabel. See `YYTextSimpleMarkdownParser` and `YYTextSimpleEmoticonParser` for example.
|
||||
*/
|
||||
@protocol YYTextParser <NSObject>
|
||||
@required
|
||||
/**
|
||||
When text is changed in YYTextView or YYLabel, this method will be called.
|
||||
|
||||
@param text The original attributed string. This method may parse the text and
|
||||
change the text attributes or content.
|
||||
|
||||
@param selectedRange Current selected range in `text`.
|
||||
This method should correct the range if the text content is changed. If there's
|
||||
no selected range (such as YYLabel), this value is NULL.
|
||||
|
||||
@return If the 'text' is modified in this method, returns `YES`, otherwise returns `NO`.
|
||||
*/
|
||||
- (BOOL)parseText:(nullable NSMutableAttributedString *)text selectedRange:(nullable NSRangePointer)selectedRange;
|
||||
@end
|
||||
|
||||
|
||||
|
||||
/**
|
||||
A simple markdown parser.
|
||||
|
||||
It'a very simple markdown parser, you can use this parser to highlight some
|
||||
small piece of markdown text.
|
||||
|
||||
This markdown parser use regular expression to parse text, slow and weak.
|
||||
If you want to write a better parser, try these projests:
|
||||
https://github.com/NimbusKit/markdown
|
||||
https://github.com/dreamwieber/AttributedMarkdown
|
||||
https://github.com/indragiek/CocoaMarkdown
|
||||
|
||||
Or you can use lex/yacc to generate your custom parser.
|
||||
*/
|
||||
@interface YYTextSimpleMarkdownParser : NSObject <YYTextParser>
|
||||
@property (nonatomic) CGFloat fontSize; ///< default is 14
|
||||
@property (nonatomic) CGFloat headerFontSize; ///< default is 20
|
||||
|
||||
@property (nullable, nonatomic, strong) UIColor *textColor;
|
||||
@property (nullable, nonatomic, strong) UIColor *controlTextColor;
|
||||
@property (nullable, nonatomic, strong) UIColor *headerTextColor;
|
||||
@property (nullable, nonatomic, strong) UIColor *inlineTextColor;
|
||||
@property (nullable, nonatomic, strong) UIColor *codeTextColor;
|
||||
@property (nullable, nonatomic, strong) UIColor *linkTextColor;
|
||||
|
||||
- (void)setColorWithBrightTheme; ///< reset the color properties to pre-defined value.
|
||||
- (void)setColorWithDarkTheme; ///< reset the color properties to pre-defined value.
|
||||
@end
|
||||
|
||||
|
||||
|
||||
/**
|
||||
A simple emoticon parser.
|
||||
|
||||
Use this parser to map some specified piece of string to image emoticon.
|
||||
Example: "Hello :smile:" -> "Hello 😀"
|
||||
|
||||
It can also be used to extend the "unicode emoticon".
|
||||
*/
|
||||
@interface YYTextSimpleEmoticonParser : NSObject <YYTextParser>
|
||||
|
||||
/**
|
||||
The custom emoticon mapper.
|
||||
The key is a specified plain string, such as @":smile:".
|
||||
The value is a UIImage which will replace the specified plain string in text.
|
||||
*/
|
||||
@property (nullable, copy) NSDictionary<NSString *, __kindof UIImage *> *emoticonMapper;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,417 @@
|
|||
//
|
||||
// YYTextParser.m
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/3/6.
|
||||
// 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 "YYTextParser.h"
|
||||
#import "YYTextUtilities.h"
|
||||
#import "YYTextAttribute.h"
|
||||
#import "NSAttributedString+YYText.h"
|
||||
#import "NSParagraphStyle+YYText.h"
|
||||
|
||||
|
||||
#pragma mark - Markdown Parser
|
||||
|
||||
@implementation YYTextSimpleMarkdownParser {
|
||||
UIFont *_font;
|
||||
NSMutableArray *_headerFonts; ///< h1~h6
|
||||
UIFont *_boldFont;
|
||||
UIFont *_italicFont;
|
||||
UIFont *_boldItalicFont;
|
||||
UIFont *_monospaceFont;
|
||||
YYTextBorder *_border;
|
||||
|
||||
NSRegularExpression *_regexEscape; ///< escape
|
||||
NSRegularExpression *_regexHeader; ///< #header
|
||||
NSRegularExpression *_regexH1; ///< header\n====
|
||||
NSRegularExpression *_regexH2; ///< header\n----
|
||||
NSRegularExpression *_regexBreakline; ///< ******
|
||||
NSRegularExpression *_regexEmphasis; ///< *text* _text_
|
||||
NSRegularExpression *_regexStrong; ///< **text**
|
||||
NSRegularExpression *_regexStrongEmphasis; ///< ***text*** ___text___
|
||||
NSRegularExpression *_regexUnderline; ///< __text__
|
||||
NSRegularExpression *_regexStrikethrough; ///< ~~text~~
|
||||
NSRegularExpression *_regexInlineCode; ///< `text`
|
||||
NSRegularExpression *_regexLink; ///< [name](link)
|
||||
NSRegularExpression *_regexLinkRefer; ///< [ref]:
|
||||
NSRegularExpression *_regexList; ///< 1.text 2.text 3.text
|
||||
NSRegularExpression *_regexBlockQuote; ///< > quote
|
||||
NSRegularExpression *_regexCodeBlock; ///< \tcode \tcode
|
||||
NSRegularExpression *_regexNotEmptyLine;
|
||||
}
|
||||
|
||||
- (void)initRegex {
|
||||
#define regexp(reg, option) [NSRegularExpression regularExpressionWithPattern : @reg options : option error : NULL]
|
||||
_regexEscape = regexp("(\\\\\\\\|\\\\\\`|\\\\\\*|\\\\\\_|\\\\\\(|\\\\\\)|\\\\\\[|\\\\\\]|\\\\#|\\\\\\+|\\\\\\-|\\\\\\!)", 0);
|
||||
_regexHeader = regexp("^((\\#{1,6}[^#].*)|(\\#{6}.+))$", NSRegularExpressionAnchorsMatchLines);
|
||||
_regexH1 = regexp("^[^=\\n][^\\n]*\\n=+$", NSRegularExpressionAnchorsMatchLines);
|
||||
_regexH2 = regexp("^[^-\\n][^\\n]*\\n-+$", NSRegularExpressionAnchorsMatchLines);
|
||||
_regexBreakline = regexp("^[ \\t]*([*-])[ \\t]*((\\1)[ \\t]*){2,}[ \\t]*$", NSRegularExpressionAnchorsMatchLines);
|
||||
_regexEmphasis = regexp("((?<!\\*)\\*(?=[^ \\t*])(.+?)(?<=[^ \\t*])\\*(?!\\*)|(?<!_)_(?=[^ \\t_])(.+?)(?<=[^ \\t_])_(?!_))", 0);
|
||||
_regexStrong = regexp("(?<!\\*)\\*{2}(?=[^ \\t*])(.+?)(?<=[^ \\t*])\\*{2}(?!\\*)", 0);
|
||||
_regexStrongEmphasis = regexp("((?<!\\*)\\*{3}(?=[^ \\t*])(.+?)(?<=[^ \\t*])\\*{3}(?!\\*)|(?<!_)_{3}(?=[^ \\t_])(.+?)(?<=[^ \\t_])_{3}(?!_))", 0);
|
||||
_regexUnderline = regexp("(?<!_)__(?=[^ \\t_])(.+?)(?<=[^ \\t_])\\__(?!_)", 0);
|
||||
_regexStrikethrough = regexp("(?<!~)~~(?=[^ \\t~])(.+?)(?<=[^ \\t~])\\~~(?!~)", 0);
|
||||
_regexInlineCode = regexp("(?<!`)(`{1,3})([^`\n]+?)\\1(?!`)", 0);
|
||||
_regexLink = regexp("!?\\[([^\\[\\]]+)\\](\\(([^\\(\\)]+)\\)|\\[([^\\[\\]]+)\\])", 0);
|
||||
_regexLinkRefer = regexp("^[ \\t]*\\[[^\\[\\]]\\]:", NSRegularExpressionAnchorsMatchLines);
|
||||
_regexList = regexp("^[ \\t]*([*+-]|\\d+[.])[ \\t]+", NSRegularExpressionAnchorsMatchLines);
|
||||
_regexBlockQuote = regexp("^[ \\t]*>[ \\t>]*", NSRegularExpressionAnchorsMatchLines);
|
||||
_regexCodeBlock = regexp("(^\\s*$\\n)((( {4}|\\t).*(\\n|\\z))|(^\\s*$\\n))+", NSRegularExpressionAnchorsMatchLines);
|
||||
_regexNotEmptyLine = regexp("^[ \\t]*[^ \\t]+[ \\t]*$", NSRegularExpressionAnchorsMatchLines);
|
||||
#undef regexp
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
_fontSize = 14;
|
||||
_headerFontSize = 20;
|
||||
[self _updateFonts];
|
||||
[self setColorWithBrightTheme];
|
||||
[self initRegex];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setFontSize:(CGFloat)fontSize {
|
||||
if (fontSize < 1) fontSize = 12;
|
||||
_fontSize = fontSize;
|
||||
[self _updateFonts];
|
||||
}
|
||||
|
||||
- (void)setHeaderFontSize:(CGFloat)headerFontSize {
|
||||
if (headerFontSize < 1) headerFontSize = 20;
|
||||
_headerFontSize = headerFontSize;
|
||||
[self _updateFonts];
|
||||
}
|
||||
|
||||
- (void)_updateFonts {
|
||||
_font = [UIFont systemFontOfSize:_fontSize];
|
||||
_headerFonts = [NSMutableArray new];
|
||||
for (int i = 0; i < 6; i++) {
|
||||
CGFloat size = _headerFontSize - (_headerFontSize - _fontSize) / 5.0 * i;
|
||||
[_headerFonts addObject:[UIFont systemFontOfSize:size]];
|
||||
}
|
||||
_boldFont = YYTextFontWithBold(_font);
|
||||
_italicFont = YYTextFontWithItalic(_font);
|
||||
_boldItalicFont = YYTextFontWithBoldItalic(_font);
|
||||
_monospaceFont = [UIFont fontWithName:@"Menlo" size:_fontSize]; // Since iOS 7
|
||||
if (!_monospaceFont) _monospaceFont = [UIFont fontWithName:@"Courier" size:_fontSize]; // Since iOS 3
|
||||
}
|
||||
|
||||
- (void)setColorWithBrightTheme {
|
||||
_textColor = [UIColor blackColor];
|
||||
_controlTextColor = [UIColor colorWithWhite:0.749 alpha:1.000];
|
||||
_headerTextColor = [UIColor colorWithRed:1.000 green:0.502 blue:0.000 alpha:1.000];
|
||||
_inlineTextColor = [UIColor colorWithWhite:0.150 alpha:1.000];
|
||||
_codeTextColor = [UIColor colorWithWhite:0.150 alpha:1.000];
|
||||
_linkTextColor = [UIColor colorWithRed:0.000 green:0.478 blue:0.962 alpha:1.000];
|
||||
|
||||
_border = [YYTextBorder new];
|
||||
_border.lineStyle = YYTextLineStyleSingle;
|
||||
_border.fillColor = [UIColor colorWithWhite:0.184 alpha:0.090];
|
||||
_border.strokeColor = [UIColor colorWithWhite:0.546 alpha:0.650];
|
||||
_border.insets = UIEdgeInsetsMake(-1, 0, -1, 0);
|
||||
_border.cornerRadius = 2;
|
||||
_border.strokeWidth = YYTextCGFloatFromPixel(1);
|
||||
}
|
||||
|
||||
- (void)setColorWithDarkTheme {
|
||||
_textColor = [UIColor whiteColor];
|
||||
_controlTextColor = [UIColor colorWithWhite:0.604 alpha:1.000];
|
||||
_headerTextColor = [UIColor colorWithRed:0.558 green:1.000 blue:0.502 alpha:1.000];
|
||||
_inlineTextColor = [UIColor colorWithRed:1.000 green:0.862 blue:0.387 alpha:1.000];
|
||||
_codeTextColor = [UIColor colorWithWhite:0.906 alpha:1.000];
|
||||
_linkTextColor = [UIColor colorWithRed:0.000 green:0.646 blue:1.000 alpha:1.000];
|
||||
|
||||
_border = [YYTextBorder new];
|
||||
_border.lineStyle = YYTextLineStyleSingle;
|
||||
_border.fillColor = [UIColor colorWithWhite:0.820 alpha:0.130];
|
||||
_border.strokeColor = [UIColor colorWithWhite:1.000 alpha:0.280];
|
||||
_border.insets = UIEdgeInsetsMake(-1, 0, -1, 0);
|
||||
_border.cornerRadius = 2;
|
||||
_border.strokeWidth = YYTextCGFloatFromPixel(1);
|
||||
}
|
||||
|
||||
- (NSUInteger)lenghOfBeginWhiteInString:(NSString *)str withRange:(NSRange)range{
|
||||
for (NSUInteger i = 0; i < range.length; i++) {
|
||||
unichar c = [str characterAtIndex:i + range.location];
|
||||
if (c != ' ' && c != '\t' && c != '\n') return i;
|
||||
}
|
||||
return str.length;
|
||||
}
|
||||
|
||||
- (NSUInteger)lenghOfEndWhiteInString:(NSString *)str withRange:(NSRange)range{
|
||||
for (NSInteger i = range.length - 1; i >= 0; i--) {
|
||||
unichar c = [str characterAtIndex:i + range.location];
|
||||
if (c != ' ' && c != '\t' && c != '\n') return range.length - i;
|
||||
}
|
||||
return str.length;
|
||||
}
|
||||
|
||||
- (NSUInteger)lenghOfBeginChar:(unichar)c inString:(NSString *)str withRange:(NSRange)range{
|
||||
for (NSUInteger i = 0; i < range.length; i++) {
|
||||
if ([str characterAtIndex:i + range.location] != c) return i;
|
||||
}
|
||||
return str.length;
|
||||
}
|
||||
|
||||
- (BOOL)parseText:(NSMutableAttributedString *)text selectedRange:(NSRangePointer)range {
|
||||
if (text.length == 0) return NO;
|
||||
[text yy_removeAttributesInRange:NSMakeRange(0, text.length)];
|
||||
text.yy_font = _font;
|
||||
text.yy_color = _textColor;
|
||||
|
||||
NSMutableString *str = text.string.mutableCopy;
|
||||
[_regexEscape replaceMatchesInString:str options:kNilOptions range:NSMakeRange(0, str.length) withTemplate:@"@@"];
|
||||
|
||||
[_regexHeader enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
NSRange r = result.range;
|
||||
NSUInteger whiteLen = [self lenghOfBeginWhiteInString:str withRange:r];
|
||||
NSUInteger sharpLen = [self lenghOfBeginChar:'#' inString:str withRange:NSMakeRange(r.location + whiteLen, r.length - whiteLen)];
|
||||
if (sharpLen > 6) sharpLen = 6;
|
||||
[text yy_setColor:_controlTextColor range:NSMakeRange(r.location, whiteLen + sharpLen)];
|
||||
[text yy_setColor:_headerTextColor range:NSMakeRange(r.location + whiteLen + sharpLen, r.length - whiteLen - sharpLen)];
|
||||
[text yy_setFont:_headerFonts[sharpLen - 1] range:result.range];
|
||||
}];
|
||||
|
||||
[_regexH1 enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
NSRange r = result.range;
|
||||
NSRange linebreak = [str rangeOfString:@"\n" options:0 range:result.range locale:nil];
|
||||
if (linebreak.location != NSNotFound) {
|
||||
[text yy_setColor:_headerTextColor range:NSMakeRange(r.location, linebreak.location - r.location)];
|
||||
[text yy_setFont:_headerFonts[0] range:NSMakeRange(r.location, linebreak.location - r.location + 1)];
|
||||
[text yy_setColor:_controlTextColor range:NSMakeRange(linebreak.location + linebreak.length, r.location + r.length - linebreak.location - linebreak.length)];
|
||||
}
|
||||
}];
|
||||
|
||||
[_regexH2 enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
NSRange r = result.range;
|
||||
NSRange linebreak = [str rangeOfString:@"\n" options:0 range:result.range locale:nil];
|
||||
if (linebreak.location != NSNotFound) {
|
||||
[text yy_setColor:_headerTextColor range:NSMakeRange(r.location, linebreak.location - r.location)];
|
||||
[text yy_setFont:_headerFonts[1] range:NSMakeRange(r.location, linebreak.location - r.location + 1)];
|
||||
[text yy_setColor:_controlTextColor range:NSMakeRange(linebreak.location + linebreak.length, r.location + r.length - linebreak.location - linebreak.length)];
|
||||
}
|
||||
}];
|
||||
|
||||
[_regexBreakline enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
[text yy_setColor:_controlTextColor range:result.range];
|
||||
}];
|
||||
|
||||
[_regexEmphasis enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
NSRange r = result.range;
|
||||
[text yy_setColor:_controlTextColor range:NSMakeRange(r.location, 1)];
|
||||
[text yy_setColor:_controlTextColor range:NSMakeRange(r.location + r.length - 1, 1)];
|
||||
[text yy_setFont:_italicFont range:NSMakeRange(r.location + 1, r.length - 2)];
|
||||
}];
|
||||
|
||||
[_regexStrong enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
NSRange r = result.range;
|
||||
[text yy_setColor:_controlTextColor range:NSMakeRange(r.location, 2)];
|
||||
[text yy_setColor:_controlTextColor range:NSMakeRange(r.location + r.length - 2, 2)];
|
||||
[text yy_setFont:_boldFont range:NSMakeRange(r.location + 2, r.length - 4)];
|
||||
}];
|
||||
|
||||
[_regexStrongEmphasis enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
NSRange r = result.range;
|
||||
[text yy_setColor:_controlTextColor range:NSMakeRange(r.location, 3)];
|
||||
[text yy_setColor:_controlTextColor range:NSMakeRange(r.location + r.length - 3, 3)];
|
||||
[text yy_setFont:_boldItalicFont range:NSMakeRange(r.location + 3, r.length - 6)];
|
||||
}];
|
||||
|
||||
[_regexUnderline enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
NSRange r = result.range;
|
||||
[text yy_setColor:_controlTextColor range:NSMakeRange(r.location, 2)];
|
||||
[text yy_setColor:_controlTextColor range:NSMakeRange(r.location + r.length - 2, 2)];
|
||||
[text yy_setTextUnderline:[YYTextDecoration decorationWithStyle:YYTextLineStyleSingle width:@1 color:nil] range:NSMakeRange(r.location + 2, r.length - 4)];
|
||||
}];
|
||||
|
||||
[_regexStrikethrough enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
NSRange r = result.range;
|
||||
[text yy_setColor:_controlTextColor range:NSMakeRange(r.location, 2)];
|
||||
[text yy_setColor:_controlTextColor range:NSMakeRange(r.location + r.length - 2, 2)];
|
||||
[text yy_setTextStrikethrough:[YYTextDecoration decorationWithStyle:YYTextLineStyleSingle width:@1 color:nil] range:NSMakeRange(r.location + 2, r.length - 4)];
|
||||
}];
|
||||
|
||||
[_regexInlineCode enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
NSRange r = result.range;
|
||||
NSUInteger len = [self lenghOfBeginChar:'`' inString:str withRange:r];
|
||||
[text yy_setColor:_controlTextColor range:NSMakeRange(r.location, len)];
|
||||
[text yy_setColor:_controlTextColor range:NSMakeRange(r.location + r.length - len, len)];
|
||||
[text yy_setColor:_inlineTextColor range:NSMakeRange(r.location + len, r.length - 2 * len)];
|
||||
[text yy_setFont:_monospaceFont range:r];
|
||||
[text yy_setTextBorder:_border.copy range:r];
|
||||
}];
|
||||
|
||||
[_regexLink enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
NSRange r = result.range;
|
||||
[text yy_setColor:_linkTextColor range:r];
|
||||
}];
|
||||
|
||||
[_regexLinkRefer enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
NSRange r = result.range;
|
||||
[text yy_setColor:_controlTextColor range:r];
|
||||
}];
|
||||
|
||||
[_regexList enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
NSRange r = result.range;
|
||||
[text yy_setColor:_controlTextColor range:r];
|
||||
}];
|
||||
|
||||
[_regexBlockQuote enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
NSRange r = result.range;
|
||||
[text yy_setColor:_controlTextColor range:r];
|
||||
}];
|
||||
|
||||
[_regexCodeBlock enumerateMatchesInString:str options:0 range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
NSRange r = result.range;
|
||||
NSRange firstLineRange = [_regexNotEmptyLine rangeOfFirstMatchInString:str options:kNilOptions range:r];
|
||||
NSUInteger lenStart = (firstLineRange.location != NSNotFound) ? firstLineRange.location - r.location : 0;
|
||||
NSUInteger lenEnd = [self lenghOfEndWhiteInString:str withRange:r];
|
||||
if (lenStart + lenEnd < r.length) {
|
||||
NSRange codeR = NSMakeRange(r.location + lenStart, r.length - lenStart - lenEnd);
|
||||
[text yy_setColor:_codeTextColor range:codeR];
|
||||
[text yy_setFont:_monospaceFont range:codeR];
|
||||
YYTextBorder *border = [YYTextBorder new];
|
||||
border.lineStyle = YYTextLineStyleSingle;
|
||||
border.fillColor = [UIColor colorWithWhite:0.184 alpha:0.090];
|
||||
border.strokeColor = [UIColor colorWithWhite:0.200 alpha:0.300];
|
||||
border.insets = UIEdgeInsetsMake(-1, 0, -1, 0);
|
||||
border.cornerRadius = 3;
|
||||
border.strokeWidth = YYTextCGFloatFromPixel(2);
|
||||
[text yy_setTextBlockBorder:_border.copy range:codeR];
|
||||
}
|
||||
}];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
#pragma mark - Emoticon Parser
|
||||
|
||||
#define LOCK(...) dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \
|
||||
__VA_ARGS__; \
|
||||
dispatch_semaphore_signal(_lock);
|
||||
|
||||
@implementation YYTextSimpleEmoticonParser {
|
||||
NSRegularExpression *_regex;
|
||||
NSDictionary *_mapper;
|
||||
dispatch_semaphore_t _lock;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
_lock = dispatch_semaphore_create(1);
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSDictionary *)emoticonMapper {
|
||||
LOCK(NSDictionary *mapper = _mapper); return mapper;
|
||||
}
|
||||
|
||||
- (void)setEmoticonMapper:(NSDictionary *)emoticonMapper {
|
||||
LOCK(
|
||||
_mapper = emoticonMapper.copy;
|
||||
if (_mapper.count == 0) {
|
||||
_regex = nil;
|
||||
} else {
|
||||
NSMutableString *pattern = @"(".mutableCopy;
|
||||
NSArray *allKeys = _mapper.allKeys;
|
||||
NSCharacterSet *charset = [NSCharacterSet characterSetWithCharactersInString:@"$^?+*.,#|{}[]()\\"];
|
||||
for (NSUInteger i = 0, max = allKeys.count; i < max; i++) {
|
||||
NSMutableString *one = [allKeys[i] mutableCopy];
|
||||
|
||||
// escape regex characters
|
||||
for (NSUInteger ci = 0, cmax = one.length; ci < cmax; ci++) {
|
||||
unichar c = [one characterAtIndex:ci];
|
||||
if ([charset characterIsMember:c]) {
|
||||
[one insertString:@"\\" atIndex:ci];
|
||||
ci++;
|
||||
cmax++;
|
||||
}
|
||||
}
|
||||
|
||||
[pattern appendString:one];
|
||||
if (i != max - 1) [pattern appendString:@"|"];
|
||||
}
|
||||
[pattern appendString:@")"];
|
||||
_regex = [[NSRegularExpression alloc] initWithPattern:pattern options:kNilOptions error:nil];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// correct the selected range during text replacement
|
||||
- (NSRange)_replaceTextInRange:(NSRange)range withLength:(NSUInteger)length selectedRange:(NSRange)selectedRange {
|
||||
// no change
|
||||
if (range.length == length) return selectedRange;
|
||||
// right
|
||||
if (range.location >= selectedRange.location + selectedRange.length) return selectedRange;
|
||||
// left
|
||||
if (selectedRange.location >= range.location + range.length) {
|
||||
selectedRange.location = selectedRange.location + length - range.length;
|
||||
return selectedRange;
|
||||
}
|
||||
// same
|
||||
if (NSEqualRanges(range, selectedRange)) {
|
||||
selectedRange.length = length;
|
||||
return selectedRange;
|
||||
}
|
||||
// one edge same
|
||||
if ((range.location == selectedRange.location && range.length < selectedRange.length) ||
|
||||
(range.location + range.length == selectedRange.location + selectedRange.length && range.length < selectedRange.length)) {
|
||||
selectedRange.length = selectedRange.length + length - range.length;
|
||||
return selectedRange;
|
||||
}
|
||||
selectedRange.location = range.location + length;
|
||||
selectedRange.length = 0;
|
||||
return selectedRange;
|
||||
}
|
||||
|
||||
- (BOOL)parseText:(NSMutableAttributedString *)text selectedRange:(NSRangePointer)range {
|
||||
if (text.length == 0) return NO;
|
||||
|
||||
NSDictionary *mapper;
|
||||
NSRegularExpression *regex;
|
||||
LOCK(mapper = _mapper; regex = _regex;);
|
||||
if (mapper.count == 0 || regex == nil) return NO;
|
||||
|
||||
NSArray *matches = [regex matchesInString:text.string options:kNilOptions range:NSMakeRange(0, text.length)];
|
||||
if (matches.count == 0) return NO;
|
||||
|
||||
NSRange selectedRange = range ? *range : NSMakeRange(0, 0);
|
||||
NSUInteger cutLength = 0;
|
||||
for (NSUInteger i = 0, max = matches.count; i < max; i++) {
|
||||
NSTextCheckingResult *one = matches[i];
|
||||
NSRange oneRange = one.range;
|
||||
if (oneRange.length == 0) continue;
|
||||
oneRange.location -= cutLength;
|
||||
NSString *subStr = [text.string substringWithRange:oneRange];
|
||||
UIImage *emoticon = mapper[subStr];
|
||||
if (!emoticon) continue;
|
||||
|
||||
CGFloat fontSize = 12; // CoreText default value
|
||||
CTFontRef font = (__bridge CTFontRef)([text yy_attribute:NSFontAttributeName atIndex:oneRange.location]);
|
||||
if (font) fontSize = CTFontGetSize(font);
|
||||
NSMutableAttributedString *atr = [NSAttributedString yy_attachmentStringWithEmojiImage:emoticon fontSize:fontSize];
|
||||
[atr yy_setTextBackedString:[YYTextBackedString stringWithString:subStr] range:NSMakeRange(0, atr.length)];
|
||||
[text replaceCharactersInRange:oneRange withString:atr.string];
|
||||
[text yy_removeDiscontinuousAttributesInRange:NSMakeRange(oneRange.location, atr.length)];
|
||||
[text addAttributes:atr.yy_attributes range:NSMakeRange(oneRange.location, atr.length)];
|
||||
selectedRange = [self _replaceTextInRange:oneRange withLength:atr.length selectedRange:selectedRange];
|
||||
cutLength += oneRange.length - 1;
|
||||
}
|
||||
if (range) *range = selectedRange;
|
||||
|
||||
return YES;
|
||||
}
|
||||
@end
|
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// YYTextRubyAnnotation.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/4/24.
|
||||
// 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 <CoreText/CoreText.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
Wrapper for CTRubyAnnotationRef.
|
||||
|
||||
Example:
|
||||
|
||||
YYTextRubyAnnotation *ruby = [YYTextRubyAnnotation new];
|
||||
ruby.textBefore = @"zhù yīn";
|
||||
CTRubyAnnotationRef ctRuby = ruby.CTRubyAnnotation;
|
||||
if (ctRuby) {
|
||||
/// add to attributed string
|
||||
CFRelease(ctRuby);
|
||||
}
|
||||
|
||||
*/
|
||||
@interface YYTextRubyAnnotation : NSObject <NSCopying, NSCoding>
|
||||
|
||||
/// Specifies how the ruby text and the base text should be aligned relative to each other.
|
||||
@property (nonatomic) CTRubyAlignment alignment;
|
||||
|
||||
/// Specifies how the ruby text can overhang adjacent characters.
|
||||
@property (nonatomic) CTRubyOverhang overhang;
|
||||
|
||||
/// Specifies the size of the annotation text as a percent of the size of the base text.
|
||||
@property (nonatomic) CGFloat sizeFactor;
|
||||
|
||||
|
||||
/// The ruby text is positioned before the base text;
|
||||
/// i.e. above horizontal text and to the right of vertical text.
|
||||
@property (nullable, nonatomic, copy) NSString *textBefore;
|
||||
|
||||
/// The ruby text is positioned after the base text;
|
||||
/// i.e. below horizontal text and to the left of vertical text.
|
||||
@property (nullable, nonatomic, copy) NSString *textAfter;
|
||||
|
||||
/// The ruby text is positioned to the right of the base text whether it is horizontal or vertical.
|
||||
/// This is the way that Bopomofo annotations are attached to Chinese text in Taiwan.
|
||||
@property (nullable, nonatomic, copy) NSString *textInterCharacter;
|
||||
|
||||
/// The ruby text follows the base text with no special styling.
|
||||
@property (nullable, nonatomic, copy) NSString *textInline;
|
||||
|
||||
|
||||
/**
|
||||
Create a ruby object from CTRuby object.
|
||||
|
||||
@param ctRuby A CTRuby object.
|
||||
|
||||
@return A ruby object, or nil when an error occurs.
|
||||
*/
|
||||
+ (instancetype)rubyWithCTRubyRef:(CTRubyAnnotationRef)ctRuby NS_AVAILABLE_IOS(8_0);
|
||||
|
||||
/**
|
||||
Create a CTRuby object from the instance.
|
||||
|
||||
@return A new CTRuby object, or NULL when an error occurs.
|
||||
The returned value should be release after used.
|
||||
*/
|
||||
- (nullable CTRubyAnnotationRef)CTRubyAnnotation CF_RETURNS_RETAINED NS_AVAILABLE_IOS(8_0);
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,83 @@
|
|||
//
|
||||
// YYTextRubyAnnotation.m
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/4/24.
|
||||
// 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 "YYTextRubyAnnotation.h"
|
||||
|
||||
@implementation YYTextRubyAnnotation
|
||||
|
||||
- (instancetype)init {
|
||||
self = super.init;
|
||||
self.alignment = kCTRubyAlignmentAuto;
|
||||
self.overhang = kCTRubyOverhangAuto;
|
||||
self.sizeFactor = 0.5;
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype)rubyWithCTRubyRef:(CTRubyAnnotationRef)ctRuby {
|
||||
if (!ctRuby) return nil;
|
||||
YYTextRubyAnnotation *one = [self new];
|
||||
one.alignment = CTRubyAnnotationGetAlignment(ctRuby);
|
||||
one.overhang = CTRubyAnnotationGetOverhang(ctRuby);
|
||||
one.sizeFactor = CTRubyAnnotationGetSizeFactor(ctRuby);
|
||||
one.textBefore = (__bridge NSString *)(CTRubyAnnotationGetTextForPosition(ctRuby, kCTRubyPositionBefore));
|
||||
one.textAfter = (__bridge NSString *)(CTRubyAnnotationGetTextForPosition(ctRuby, kCTRubyPositionAfter));
|
||||
one.textInterCharacter = (__bridge NSString *)(CTRubyAnnotationGetTextForPosition(ctRuby, kCTRubyPositionInterCharacter));
|
||||
one.textInline = (__bridge NSString *)(CTRubyAnnotationGetTextForPosition(ctRuby, kCTRubyPositionInline));
|
||||
return one;
|
||||
}
|
||||
|
||||
- (CTRubyAnnotationRef)CTRubyAnnotation CF_RETURNS_RETAINED {
|
||||
if (((long)CTRubyAnnotationCreate + 1) == 1) return NULL; // system not support
|
||||
|
||||
CFStringRef text[kCTRubyPositionCount];
|
||||
text[kCTRubyPositionBefore] = (__bridge CFStringRef)(_textBefore);
|
||||
text[kCTRubyPositionAfter] = (__bridge CFStringRef)(_textAfter);
|
||||
text[kCTRubyPositionInterCharacter] = (__bridge CFStringRef)(_textInterCharacter);
|
||||
text[kCTRubyPositionInline] = (__bridge CFStringRef)(_textInline);
|
||||
CTRubyAnnotationRef ruby = CTRubyAnnotationCreate(_alignment, _overhang, _sizeFactor, text);
|
||||
return ruby;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
YYTextRubyAnnotation *one = [self.class new];
|
||||
one.alignment = _alignment;
|
||||
one.overhang = _overhang;
|
||||
one.sizeFactor = _sizeFactor;
|
||||
one.textBefore = _textBefore;
|
||||
one.textAfter = _textAfter;
|
||||
one.textInterCharacter = _textInterCharacter;
|
||||
one.textInline = _textInline;
|
||||
return one;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[aCoder encodeObject:@(_alignment) forKey:@"alignment"];
|
||||
[aCoder encodeObject:@(_overhang) forKey:@"overhang"];
|
||||
[aCoder encodeObject:@(_sizeFactor) forKey:@"sizeFactor"];
|
||||
[aCoder encodeObject:_textBefore forKey:@"textBefore"];
|
||||
[aCoder encodeObject:_textAfter forKey:@"textAfter"];
|
||||
[aCoder encodeObject:_textInterCharacter forKey:@"textInterCharacter"];
|
||||
[aCoder encodeObject:_textInline forKey:@"textInline"];
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [self init];
|
||||
_alignment = ((NSNumber *)[aDecoder decodeObjectForKey:@"alignment"]).intValue;
|
||||
_overhang = ((NSNumber *)[aDecoder decodeObjectForKey:@"overhang"]).intValue;
|
||||
_sizeFactor = ((NSNumber *)[aDecoder decodeObjectForKey:@"sizeFactor"]).intValue;
|
||||
_textBefore = [aDecoder decodeObjectForKey:@"textBefore"];
|
||||
_textAfter = [aDecoder decodeObjectForKey:@"textAfter"];
|
||||
_textInterCharacter = [aDecoder decodeObjectForKey:@"textInterCharacter"];
|
||||
_textInline = [aDecoder decodeObjectForKey:@"textInline"];
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,68 @@
|
|||
//
|
||||
// YYTextRunDelegate.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 14/10/14.
|
||||
// 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 <CoreText/CoreText.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
Wrapper for CTRunDelegateRef.
|
||||
|
||||
Example:
|
||||
|
||||
YYTextRunDelegate *delegate = [YYTextRunDelegate new];
|
||||
delegate.ascent = 20;
|
||||
delegate.descent = 4;
|
||||
delegate.width = 20;
|
||||
CTRunDelegateRef ctRunDelegate = delegate.CTRunDelegate;
|
||||
if (ctRunDelegate) {
|
||||
/// add to attributed string
|
||||
CFRelease(ctRunDelegate);
|
||||
}
|
||||
|
||||
*/
|
||||
@interface YYTextRunDelegate : NSObject <NSCopying, NSCoding>
|
||||
|
||||
/**
|
||||
Creates and returns the CTRunDelegate.
|
||||
|
||||
@discussion You need call CFRelease() after used.
|
||||
The CTRunDelegateRef has a strong reference to this YYTextRunDelegate object.
|
||||
In CoreText, use CTRunDelegateGetRefCon() to get this YYTextRunDelegate object.
|
||||
|
||||
@return The CTRunDelegate object.
|
||||
*/
|
||||
- (nullable CTRunDelegateRef)CTRunDelegate CF_RETURNS_RETAINED;
|
||||
|
||||
/**
|
||||
Additional information about the the run delegate.
|
||||
*/
|
||||
@property (nullable, nonatomic, strong) NSDictionary *userInfo;
|
||||
|
||||
/**
|
||||
The typographic ascent of glyphs in the run.
|
||||
*/
|
||||
@property (nonatomic) CGFloat ascent;
|
||||
|
||||
/**
|
||||
The typographic descent of glyphs in the run.
|
||||
*/
|
||||
@property (nonatomic) CGFloat descent;
|
||||
|
||||
/**
|
||||
The typographic width of glyphs in the run.
|
||||
*/
|
||||
@property (nonatomic) CGFloat width;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,71 @@
|
|||
//
|
||||
// YYTextRunDelegate.m
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 14/10/14.
|
||||
// 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 "YYTextRunDelegate.h"
|
||||
|
||||
static void DeallocCallback(void *ref) {
|
||||
YYTextRunDelegate *self = (__bridge_transfer YYTextRunDelegate *)(ref);
|
||||
self = nil; // release
|
||||
}
|
||||
|
||||
static CGFloat GetAscentCallback(void *ref) {
|
||||
YYTextRunDelegate *self = (__bridge YYTextRunDelegate *)(ref);
|
||||
return self.ascent;
|
||||
}
|
||||
|
||||
static CGFloat GetDecentCallback(void *ref) {
|
||||
YYTextRunDelegate *self = (__bridge YYTextRunDelegate *)(ref);
|
||||
return self.descent;
|
||||
}
|
||||
|
||||
static CGFloat GetWidthCallback(void *ref) {
|
||||
YYTextRunDelegate *self = (__bridge YYTextRunDelegate *)(ref);
|
||||
return self.width;
|
||||
}
|
||||
|
||||
@implementation YYTextRunDelegate
|
||||
|
||||
- (CTRunDelegateRef)CTRunDelegate CF_RETURNS_RETAINED {
|
||||
CTRunDelegateCallbacks callbacks;
|
||||
callbacks.version = kCTRunDelegateCurrentVersion;
|
||||
callbacks.dealloc = DeallocCallback;
|
||||
callbacks.getAscent = GetAscentCallback;
|
||||
callbacks.getDescent = GetDecentCallback;
|
||||
callbacks.getWidth = GetWidthCallback;
|
||||
return CTRunDelegateCreate(&callbacks, (__bridge_retained void *)(self.copy));
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
||||
[aCoder encodeObject:@(_ascent) forKey:@"ascent"];
|
||||
[aCoder encodeObject:@(_descent) forKey:@"descent"];
|
||||
[aCoder encodeObject:@(_width) forKey:@"width"];
|
||||
[aCoder encodeObject:_userInfo forKey:@"userInfo"];
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder {
|
||||
self = [super init];
|
||||
_ascent = ((NSNumber *)[aDecoder decodeObjectForKey:@"ascent"]).floatValue;
|
||||
_descent = ((NSNumber *)[aDecoder decodeObjectForKey:@"descent"]).floatValue;
|
||||
_width = ((NSNumber *)[aDecoder decodeObjectForKey:@"width"]).floatValue;
|
||||
_userInfo = [aDecoder decodeObjectForKey:@"userInfo"];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
typeof(self) one = [self.class new];
|
||||
one.ascent = self.ascent;
|
||||
one.descent = self.descent;
|
||||
one.width = self.width;
|
||||
one.userInfo = self.userInfo;
|
||||
return one;
|
||||
}
|
||||
|
||||
@end
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// NSParagraphStyle+YYText.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 14/10/7.
|
||||
// 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
|
||||
|
||||
/**
|
||||
Provides extensions for `NSParagraphStyle` to work with CoreText.
|
||||
*/
|
||||
@interface NSParagraphStyle (YYText)
|
||||
|
||||
/**
|
||||
Creates a new NSParagraphStyle object from the CoreText Style.
|
||||
|
||||
@param CTStyle CoreText Paragraph Style.
|
||||
|
||||
@return a new NSParagraphStyle
|
||||
*/
|
||||
+ (nullable NSParagraphStyle *)yy_styleWithCTStyle:(CTParagraphStyleRef)CTStyle;
|
||||
|
||||
/**
|
||||
Creates and returns a CoreText Paragraph Style. (need call CFRelease() after used)
|
||||
*/
|
||||
- (nullable CTParagraphStyleRef)yy_CTStyle CF_RETURNS_RETAINED;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,230 @@
|
|||
//
|
||||
// NSParagraphStyle+YYText.m
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 14/10/7.
|
||||
// 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 "NSParagraphStyle+YYText.h"
|
||||
#import "YYTextAttribute.h"
|
||||
#import <CoreText/CoreText.h>
|
||||
|
||||
// Dummy class for category
|
||||
@interface NSParagraphStyle_YYText : NSObject @end
|
||||
@implementation NSParagraphStyle_YYText @end
|
||||
|
||||
|
||||
@implementation NSParagraphStyle (YYText)
|
||||
|
||||
+ (NSParagraphStyle *)yy_styleWithCTStyle:(CTParagraphStyleRef)CTStyle {
|
||||
if (CTStyle == NULL) return nil;
|
||||
|
||||
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
CGFloat lineSpacing;
|
||||
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierLineSpacing, sizeof(CGFloat), &lineSpacing)) {
|
||||
style.lineSpacing = lineSpacing;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
CGFloat paragraphSpacing;
|
||||
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierParagraphSpacing, sizeof(CGFloat), ¶graphSpacing)) {
|
||||
style.paragraphSpacing = paragraphSpacing;
|
||||
}
|
||||
|
||||
CTTextAlignment alignment;
|
||||
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment), &alignment)) {
|
||||
style.alignment = NSTextAlignmentFromCTTextAlignment(alignment);
|
||||
}
|
||||
|
||||
CGFloat firstLineHeadIndent;
|
||||
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierFirstLineHeadIndent, sizeof(CGFloat), &firstLineHeadIndent)) {
|
||||
style.firstLineHeadIndent = firstLineHeadIndent;
|
||||
}
|
||||
|
||||
CGFloat headIndent;
|
||||
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierHeadIndent, sizeof(CGFloat), &headIndent)) {
|
||||
style.headIndent = headIndent;
|
||||
}
|
||||
|
||||
CGFloat tailIndent;
|
||||
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierTailIndent, sizeof(CGFloat), &tailIndent)) {
|
||||
style.tailIndent = tailIndent;
|
||||
}
|
||||
|
||||
CTLineBreakMode lineBreakMode;
|
||||
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &lineBreakMode)) {
|
||||
style.lineBreakMode = (NSLineBreakMode)lineBreakMode;
|
||||
}
|
||||
|
||||
CGFloat minimumLineHeight;
|
||||
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierMinimumLineHeight, sizeof(CGFloat), &minimumLineHeight)) {
|
||||
style.minimumLineHeight = minimumLineHeight;
|
||||
}
|
||||
|
||||
CGFloat maximumLineHeight;
|
||||
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierMaximumLineHeight, sizeof(CGFloat), &maximumLineHeight)) {
|
||||
style.maximumLineHeight = maximumLineHeight;
|
||||
}
|
||||
|
||||
CTWritingDirection baseWritingDirection;
|
||||
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierBaseWritingDirection, sizeof(CTWritingDirection), &baseWritingDirection)) {
|
||||
style.baseWritingDirection = (NSWritingDirection)baseWritingDirection;
|
||||
}
|
||||
|
||||
CGFloat lineHeightMultiple;
|
||||
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierLineHeightMultiple, sizeof(CGFloat), &lineHeightMultiple)) {
|
||||
style.lineHeightMultiple = lineHeightMultiple;
|
||||
}
|
||||
|
||||
CGFloat paragraphSpacingBefore;
|
||||
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierParagraphSpacingBefore, sizeof(CGFloat), ¶graphSpacingBefore)) {
|
||||
style.paragraphSpacingBefore = paragraphSpacingBefore;
|
||||
}
|
||||
|
||||
if ([style respondsToSelector:@selector(tabStops)]) {
|
||||
CFArrayRef tabStops;
|
||||
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierTabStops, sizeof(CFArrayRef), &tabStops)) {
|
||||
if ([style respondsToSelector:@selector(setTabStops:)]) {
|
||||
NSMutableArray *tabs = [NSMutableArray new];
|
||||
[((__bridge NSArray *)(tabStops))enumerateObjectsUsingBlock : ^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
CTTextTabRef ctTab = (__bridge CFTypeRef)obj;
|
||||
|
||||
NSTextTab *tab = [[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentFromCTTextAlignment(CTTextTabGetAlignment(ctTab)) location:CTTextTabGetLocation(ctTab) options:(__bridge id)CTTextTabGetOptions(ctTab)];
|
||||
[tabs addObject:tab];
|
||||
}];
|
||||
if (tabs.count) {
|
||||
style.tabStops = tabs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CGFloat defaultTabInterval;
|
||||
if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierDefaultTabInterval, sizeof(CGFloat), &defaultTabInterval)) {
|
||||
if ([style respondsToSelector:@selector(setDefaultTabInterval:)]) {
|
||||
style.defaultTabInterval = defaultTabInterval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
- (CTParagraphStyleRef)yy_CTStyle CF_RETURNS_RETAINED {
|
||||
CTParagraphStyleSetting set[kCTParagraphStyleSpecifierCount] = { 0 };
|
||||
int count = 0;
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
CGFloat lineSpacing = self.lineSpacing;
|
||||
set[count].spec = kCTParagraphStyleSpecifierLineSpacing;
|
||||
set[count].valueSize = sizeof(CGFloat);
|
||||
set[count].value = &lineSpacing;
|
||||
count++;
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
CGFloat paragraphSpacing = self.paragraphSpacing;
|
||||
set[count].spec = kCTParagraphStyleSpecifierParagraphSpacing;
|
||||
set[count].valueSize = sizeof(CGFloat);
|
||||
set[count].value = ¶graphSpacing;
|
||||
count++;
|
||||
|
||||
CTTextAlignment alignment = NSTextAlignmentToCTTextAlignment(self.alignment);
|
||||
set[count].spec = kCTParagraphStyleSpecifierAlignment;
|
||||
set[count].valueSize = sizeof(CTTextAlignment);
|
||||
set[count].value = &alignment;
|
||||
count++;
|
||||
|
||||
CGFloat firstLineHeadIndent = self.firstLineHeadIndent;
|
||||
set[count].spec = kCTParagraphStyleSpecifierFirstLineHeadIndent;
|
||||
set[count].valueSize = sizeof(CGFloat);
|
||||
set[count].value = &firstLineHeadIndent;
|
||||
count++;
|
||||
|
||||
CGFloat headIndent = self.headIndent;
|
||||
set[count].spec = kCTParagraphStyleSpecifierHeadIndent;
|
||||
set[count].valueSize = sizeof(CGFloat);
|
||||
set[count].value = &headIndent;
|
||||
count++;
|
||||
|
||||
CGFloat tailIndent = self.tailIndent;
|
||||
set[count].spec = kCTParagraphStyleSpecifierTailIndent;
|
||||
set[count].valueSize = sizeof(CGFloat);
|
||||
set[count].value = &tailIndent;
|
||||
count++;
|
||||
|
||||
CTLineBreakMode paraLineBreak = (CTLineBreakMode)self.lineBreakMode;
|
||||
set[count].spec = kCTParagraphStyleSpecifierLineBreakMode;
|
||||
set[count].valueSize = sizeof(CTLineBreakMode);
|
||||
set[count].value = ¶LineBreak;
|
||||
count++;
|
||||
|
||||
CGFloat minimumLineHeight = self.minimumLineHeight;
|
||||
set[count].spec = kCTParagraphStyleSpecifierMinimumLineHeight;
|
||||
set[count].valueSize = sizeof(CGFloat);
|
||||
set[count].value = &minimumLineHeight;
|
||||
count++;
|
||||
|
||||
CGFloat maximumLineHeight = self.maximumLineHeight;
|
||||
set[count].spec = kCTParagraphStyleSpecifierMaximumLineHeight;
|
||||
set[count].valueSize = sizeof(CGFloat);
|
||||
set[count].value = &maximumLineHeight;
|
||||
count++;
|
||||
|
||||
CTWritingDirection paraWritingDirection = (CTWritingDirection)self.baseWritingDirection;
|
||||
set[count].spec = kCTParagraphStyleSpecifierBaseWritingDirection;
|
||||
set[count].valueSize = sizeof(CTWritingDirection);
|
||||
set[count].value = ¶WritingDirection;
|
||||
count++;
|
||||
|
||||
CGFloat lineHeightMultiple = self.lineHeightMultiple;
|
||||
set[count].spec = kCTParagraphStyleSpecifierLineHeightMultiple;
|
||||
set[count].valueSize = sizeof(CGFloat);
|
||||
set[count].value = &lineHeightMultiple;
|
||||
count++;
|
||||
|
||||
CGFloat paragraphSpacingBefore = self.paragraphSpacingBefore;
|
||||
set[count].spec = kCTParagraphStyleSpecifierParagraphSpacingBefore;
|
||||
set[count].valueSize = sizeof(CGFloat);
|
||||
set[count].value = ¶graphSpacingBefore;
|
||||
count++;
|
||||
|
||||
if([self respondsToSelector:@selector(tabStops)]) {
|
||||
NSMutableArray *tabs = [NSMutableArray array];
|
||||
if ([self respondsToSelector:@selector(tabStops)]) {
|
||||
NSInteger numTabs = self.tabStops.count;
|
||||
if (numTabs) {
|
||||
[self.tabStops enumerateObjectsUsingBlock: ^(NSTextTab *tab, NSUInteger idx, BOOL *stop) {
|
||||
CTTextTabRef ctTab = CTTextTabCreate(NSTextAlignmentToCTTextAlignment(tab.alignment), tab.location, (__bridge CFTypeRef)tab.options);
|
||||
[tabs addObject:(__bridge id)ctTab];
|
||||
CFRelease(ctTab);
|
||||
}];
|
||||
|
||||
CFArrayRef tabStops = (__bridge CFArrayRef)(tabs);
|
||||
set[count].spec = kCTParagraphStyleSpecifierTabStops;
|
||||
set[count].valueSize = sizeof(CFArrayRef);
|
||||
set[count].value = &tabStops;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if ([self respondsToSelector:@selector(defaultTabInterval)]) {
|
||||
CGFloat defaultTabInterval = self.defaultTabInterval;
|
||||
set[count].spec = kCTParagraphStyleSpecifierDefaultTabInterval;
|
||||
set[count].valueSize = sizeof(CGFloat);
|
||||
set[count].value = &defaultTabInterval;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
CTParagraphStyleRef style = CTParagraphStyleCreate(set, count);
|
||||
return style;
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// UIPasteboard+YYText.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/4/2.
|
||||
// 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
|
||||
|
||||
/**
|
||||
Extend UIPasteboard to support image and attributed string.
|
||||
*/
|
||||
@interface UIPasteboard (YYText)
|
||||
|
||||
@property (nullable, nonatomic, copy) NSData *yy_PNGData; ///< PNG file data
|
||||
@property (nullable, nonatomic, copy) NSData *yy_JPEGData; ///< JPEG file data
|
||||
@property (nullable, nonatomic, copy) NSData *yy_GIFData; ///< GIF file data
|
||||
@property (nullable, nonatomic, copy) NSData *yy_WEBPData; ///< WebP file data
|
||||
@property (nullable, nonatomic, copy) NSData *yy_ImageData; ///< image file data
|
||||
|
||||
/// Attributed string,
|
||||
/// Set this attributed will also set the string property which is copy from the attributed string.
|
||||
/// If the attributed string contains one or more image, it will also set the `images` property.
|
||||
@property (nullable, nonatomic, copy) NSAttributedString *yy_AttributedString;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/// The name identifying the attributed string in pasteboard.
|
||||
UIKIT_EXTERN NSString *const YYTextPasteboardTypeAttributedString;
|
||||
|
||||
/// The UTI Type identifying WebP data in pasteboard.
|
||||
UIKIT_EXTERN NSString *const YYTextUTTypeWEBP;
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,146 @@
|
|||
//
|
||||
// UIPasteboard+YYText.m
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/4/2.
|
||||
// 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 "UIPasteboard+YYText.h"
|
||||
#import "NSAttributedString+YYText.h"
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
|
||||
|
||||
#if __has_include("YYImage.h")
|
||||
#import "YYImage.h"
|
||||
#define YYTextAnimatedImageAvailable 1
|
||||
#elif __has_include(<YYImage/YYImage.h>)
|
||||
#import <YYImage/YYImage.h>
|
||||
#define YYTextAnimatedImageAvailable 1
|
||||
#elif __has_include(<YYWebImage/YYImage.h>)
|
||||
#import <YYWebImage/YYImage.h>
|
||||
#define YYTextAnimatedImageAvailable 1
|
||||
#else
|
||||
#define YYTextAnimatedImageAvailable 0
|
||||
#endif
|
||||
|
||||
|
||||
// Dummy class for category
|
||||
@interface UIPasteboard_YYText : NSObject @end
|
||||
@implementation UIPasteboard_YYText @end
|
||||
|
||||
|
||||
NSString *const YYTextPasteboardTypeAttributedString = @"com.ibireme.NSAttributedString";
|
||||
NSString *const YYTextUTTypeWEBP = @"com.google.webp";
|
||||
|
||||
@implementation UIPasteboard (YYText)
|
||||
|
||||
|
||||
- (void)setYy_PNGData:(NSData *)PNGData {
|
||||
[self setData:PNGData forPasteboardType:(id)kUTTypePNG];
|
||||
}
|
||||
|
||||
- (NSData *)yy_PNGData {
|
||||
return [self dataForPasteboardType:(id)kUTTypePNG];
|
||||
}
|
||||
|
||||
- (void)setYy_JPEGData:(NSData *)JPEGData {
|
||||
[self setData:JPEGData forPasteboardType:(id)kUTTypeJPEG];
|
||||
}
|
||||
|
||||
- (NSData *)yy_JPEGData {
|
||||
return [self dataForPasteboardType:(id)kUTTypeJPEG];
|
||||
}
|
||||
|
||||
- (void)setYy_GIFData:(NSData *)GIFData {
|
||||
[self setData:GIFData forPasteboardType:(id)kUTTypeGIF];
|
||||
}
|
||||
|
||||
- (NSData *)yy_GIFData {
|
||||
return [self dataForPasteboardType:(id)kUTTypeGIF];
|
||||
}
|
||||
|
||||
- (void)setYy_WEBPData:(NSData *)WEBPData {
|
||||
[self setData:WEBPData forPasteboardType:YYTextUTTypeWEBP];
|
||||
}
|
||||
|
||||
- (NSData *)yy_WEBPData {
|
||||
return [self dataForPasteboardType:YYTextUTTypeWEBP];
|
||||
}
|
||||
|
||||
- (void)setYy_ImageData:(NSData *)imageData {
|
||||
[self setData:imageData forPasteboardType:(id)kUTTypeImage];
|
||||
}
|
||||
|
||||
- (NSData *)yy_ImageData {
|
||||
return [self dataForPasteboardType:(id)kUTTypeImage];
|
||||
}
|
||||
|
||||
- (void)setYy_AttributedString:(NSAttributedString *)attributedString {
|
||||
self.string = [attributedString yy_plainTextForRange:NSMakeRange(0, attributedString.length)];
|
||||
NSData *data = [attributedString yy_archiveToData];
|
||||
if (data) {
|
||||
NSDictionary *item = @{YYTextPasteboardTypeAttributedString : data};
|
||||
[self addItems:@[item]];
|
||||
}
|
||||
[attributedString enumerateAttribute:YYTextAttachmentAttributeName inRange:NSMakeRange(0, attributedString.length) options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(YYTextAttachment *attachment, NSRange range, BOOL *stop) {
|
||||
|
||||
// save image
|
||||
UIImage *simpleImage = nil;
|
||||
if ([attachment.content isKindOfClass:[UIImage class]]) {
|
||||
simpleImage = attachment.content;
|
||||
} else if ([attachment.content isKindOfClass:[UIImageView class]]) {
|
||||
simpleImage = ((UIImageView *)attachment.content).image;
|
||||
}
|
||||
if (simpleImage) {
|
||||
NSDictionary *item = @{@"com.apple.uikit.image" : simpleImage};
|
||||
[self addItems:@[item]];
|
||||
}
|
||||
|
||||
#if YYTextAnimatedImageAvailable
|
||||
// save animated image
|
||||
if ([attachment.content isKindOfClass:[UIImageView class]]) {
|
||||
UIImageView *imageView = attachment.content;
|
||||
Class aniImageClass = NSClassFromString(@"YYImage");
|
||||
UIImage *image = imageView.image;
|
||||
if (aniImageClass && [image isKindOfClass:aniImageClass]) {
|
||||
NSData *data = [image valueForKey:@"animatedImageData"];
|
||||
NSNumber *type = [image valueForKey:@"animatedImageType"];
|
||||
if (data) {
|
||||
switch (type.unsignedIntegerValue) {
|
||||
case YYImageTypeGIF: {
|
||||
NSDictionary *item = @{(id)kUTTypeGIF : data};
|
||||
[self addItems:@[item]];
|
||||
} break;
|
||||
case YYImageTypePNG: { // APNG
|
||||
NSDictionary *item = @{(id)kUTTypePNG : data};
|
||||
[self addItems:@[item]];
|
||||
} break;
|
||||
case YYImageTypeWebP: {
|
||||
NSDictionary *item = @{(id)YYTextUTTypeWEBP : data};
|
||||
[self addItems:@[item]];
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSAttributedString *)yy_AttributedString {
|
||||
for (NSDictionary *items in self.items) {
|
||||
NSData *data = items[YYTextPasteboardTypeAttributedString];
|
||||
if (data) {
|
||||
return [NSAttributedString yy_unarchiveFromData:data];
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// UIView+YYText.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// 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>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
Provides extensions for `UIView`.
|
||||
*/
|
||||
@interface UIView (YYText)
|
||||
|
||||
/**
|
||||
Returns the view's view controller (may be nil).
|
||||
*/
|
||||
@property (nullable, nonatomic, readonly) UIViewController *yy_viewController;
|
||||
|
||||
/**
|
||||
Returns the visible alpha on screen, taking into account superview and window.
|
||||
*/
|
||||
@property (nonatomic, readonly) CGFloat yy_visibleAlpha;
|
||||
|
||||
/**
|
||||
Converts a point from the receiver's coordinate system to that of the specified view or window.
|
||||
|
||||
@param point A point specified in the local coordinate system (bounds) of the receiver.
|
||||
@param view The view or window into whose coordinate system point is to be converted.
|
||||
If view is nil, this method instead converts to window base coordinates.
|
||||
@return The point converted to the coordinate system of view.
|
||||
*/
|
||||
- (CGPoint)yy_convertPoint:(CGPoint)point toViewOrWindow:(UIView *)view;
|
||||
|
||||
/**
|
||||
Converts a point from the coordinate system of a given view or window to that of the receiver.
|
||||
|
||||
@param point A point specified in the local coordinate system (bounds) of view.
|
||||
@param view The view or window with point in its coordinate system.
|
||||
If view is nil, this method instead converts from window base coordinates.
|
||||
@return The point converted to the local coordinate system (bounds) of the receiver.
|
||||
*/
|
||||
- (CGPoint)yy_convertPoint:(CGPoint)point fromViewOrWindow:(UIView *)view;
|
||||
|
||||
/**
|
||||
Converts a rectangle from the receiver's coordinate system to that of another view or window.
|
||||
|
||||
@param rect A rectangle specified in the local coordinate system (bounds) of the receiver.
|
||||
@param view The view or window that is the target of the conversion operation. If view is nil, this method instead converts to window base coordinates.
|
||||
@return The converted rectangle.
|
||||
*/
|
||||
- (CGRect)yy_convertRect:(CGRect)rect toViewOrWindow:(UIView *)view;
|
||||
|
||||
/**
|
||||
Converts a rectangle from the coordinate system of another view or window to that of the receiver.
|
||||
|
||||
@param rect A rectangle specified in the local coordinate system (bounds) of view.
|
||||
@param view The view or window with rect in its coordinate system.
|
||||
If view is nil, this method instead converts from window base coordinates.
|
||||
@return The converted rectangle.
|
||||
*/
|
||||
- (CGRect)yy_convertRect:(CGRect)rect fromViewOrWindow:(UIView *)view;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,123 @@
|
|||
//
|
||||
// UIView+YYText.m
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// 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+YYText.h"
|
||||
|
||||
// Dummy class for category
|
||||
@interface UIView_YYText : NSObject @end
|
||||
@implementation UIView_YYText @end
|
||||
|
||||
|
||||
@implementation UIView (YYText)
|
||||
|
||||
- (UIViewController *)yy_viewController {
|
||||
for (UIView *view = self; view; view = view.superview) {
|
||||
UIResponder *nextResponder = [view nextResponder];
|
||||
if ([nextResponder isKindOfClass:[UIViewController class]]) {
|
||||
return (UIViewController *)nextResponder;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CGFloat)yy_visibleAlpha {
|
||||
if ([self isKindOfClass:[UIWindow class]]) {
|
||||
if (self.hidden) return 0;
|
||||
return self.alpha;
|
||||
}
|
||||
if (!self.window) return 0;
|
||||
CGFloat alpha = 1;
|
||||
UIView *v = self;
|
||||
while (v) {
|
||||
if (v.hidden) {
|
||||
alpha = 0;
|
||||
break;
|
||||
}
|
||||
alpha *= v.alpha;
|
||||
v = v.superview;
|
||||
}
|
||||
return alpha;
|
||||
}
|
||||
|
||||
- (CGPoint)yy_convertPoint:(CGPoint)point toViewOrWindow:(UIView *)view {
|
||||
if (!view) {
|
||||
if ([self isKindOfClass:[UIWindow class]]) {
|
||||
return [((UIWindow *)self) convertPoint:point toWindow:nil];
|
||||
} else {
|
||||
return [self convertPoint:point toView:nil];
|
||||
}
|
||||
}
|
||||
|
||||
UIWindow *from = [self isKindOfClass:[UIWindow class]] ? (id)self : self.window;
|
||||
UIWindow *to = [view isKindOfClass:[UIWindow class]] ? (id)view : view.window;
|
||||
if ((!from || !to) || (from == to)) return [self convertPoint:point toView:view];
|
||||
point = [self convertPoint:point toView:from];
|
||||
point = [to convertPoint:point fromWindow:from];
|
||||
point = [view convertPoint:point fromView:to];
|
||||
return point;
|
||||
}
|
||||
|
||||
- (CGPoint)yy_convertPoint:(CGPoint)point fromViewOrWindow:(UIView *)view {
|
||||
if (!view) {
|
||||
if ([self isKindOfClass:[UIWindow class]]) {
|
||||
return [((UIWindow *)self) convertPoint:point fromWindow:nil];
|
||||
} else {
|
||||
return [self convertPoint:point fromView:nil];
|
||||
}
|
||||
}
|
||||
|
||||
UIWindow *from = [view isKindOfClass:[UIWindow class]] ? (id)view : view.window;
|
||||
UIWindow *to = [self isKindOfClass:[UIWindow class]] ? (id)self : self.window;
|
||||
if ((!from || !to) || (from == to)) return [self convertPoint:point fromView:view];
|
||||
point = [from convertPoint:point fromView:view];
|
||||
point = [to convertPoint:point fromWindow:from];
|
||||
point = [self convertPoint:point fromView:to];
|
||||
return point;
|
||||
}
|
||||
|
||||
- (CGRect)yy_convertRect:(CGRect)rect toViewOrWindow:(UIView *)view {
|
||||
if (!view) {
|
||||
if ([self isKindOfClass:[UIWindow class]]) {
|
||||
return [((UIWindow *)self) convertRect:rect toWindow:nil];
|
||||
} else {
|
||||
return [self convertRect:rect toView:nil];
|
||||
}
|
||||
}
|
||||
|
||||
UIWindow *from = [self isKindOfClass:[UIWindow class]] ? (id)self : self.window;
|
||||
UIWindow *to = [view isKindOfClass:[UIWindow class]] ? (id)view : view.window;
|
||||
if (!from || !to) return [self convertRect:rect toView:view];
|
||||
if (from == to) return [self convertRect:rect toView:view];
|
||||
rect = [self convertRect:rect toView:from];
|
||||
rect = [to convertRect:rect fromWindow:from];
|
||||
rect = [view convertRect:rect fromView:to];
|
||||
return rect;
|
||||
}
|
||||
|
||||
- (CGRect)yy_convertRect:(CGRect)rect fromViewOrWindow:(UIView *)view {
|
||||
if (!view) {
|
||||
if ([self isKindOfClass:[UIWindow class]]) {
|
||||
return [((UIWindow *)self) convertRect:rect fromWindow:nil];
|
||||
} else {
|
||||
return [self convertRect:rect fromView:nil];
|
||||
}
|
||||
}
|
||||
|
||||
UIWindow *from = [view isKindOfClass:[UIWindow class]] ? (id)view : view.window;
|
||||
UIWindow *to = [self isKindOfClass:[UIWindow class]] ? (id)self : self.window;
|
||||
if ((!from || !to) || (from == to)) return [self convertRect:rect fromView:view];
|
||||
rect = [from convertRect:rect fromView:view];
|
||||
rect = [to convertRect:rect fromWindow:from];
|
||||
rect = [self convertRect:rect fromView:to];
|
||||
return rect;
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,79 @@
|
|||
//
|
||||
// YYTextAsyncLayer.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/4/11.
|
||||
// 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>
|
||||
|
||||
@class YYTextAsyncLayerDisplayTask;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The YYTextAsyncLayer class is a subclass of CALayer used for render contents asynchronously.
|
||||
|
||||
@discussion When the layer need update it's contents, it will ask the delegate
|
||||
for a async display task to render the contents in a background queue.
|
||||
*/
|
||||
@interface YYTextAsyncLayer : CALayer
|
||||
/// Whether the render code is executed in background. Default is YES.
|
||||
@property BOOL displaysAsynchronously;
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
The YYTextAsyncLayer's delegate protocol. The delegate of the YYTextAsyncLayer (typically a UIView)
|
||||
must implements the method in this protocol.
|
||||
*/
|
||||
@protocol YYTextAsyncLayerDelegate <NSObject>
|
||||
@required
|
||||
/// This method is called to return a new display task when the layer's contents need update.
|
||||
- (YYTextAsyncLayerDisplayTask *)newAsyncDisplayTask;
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
A display task used by YYTextAsyncLayer to render the contents in background queue.
|
||||
*/
|
||||
@interface YYTextAsyncLayerDisplayTask : NSObject
|
||||
|
||||
/**
|
||||
This block will be called before the asynchronous drawing begins.
|
||||
It will be called on the main thread.
|
||||
|
||||
@param layer The layer.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) void (^willDisplay)(CALayer *layer);
|
||||
|
||||
/**
|
||||
This block is called to draw the layer's contents.
|
||||
|
||||
@discussion This block may be called on main thread or background thread,
|
||||
so is should be thread-safe.
|
||||
|
||||
@param context A new bitmap content created by layer.
|
||||
@param size The content size (typically same as layer's bound size).
|
||||
@param isCancelled If this block returns `YES`, the method should cancel the
|
||||
drawing process and return as quickly as possible.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) void (^display)(CGContextRef context, CGSize size, BOOL(^isCancelled)(void));
|
||||
|
||||
/**
|
||||
This block will be called after the asynchronous drawing finished.
|
||||
It will be called on the main thread.
|
||||
|
||||
@param layer The layer.
|
||||
@param finished If the draw process is cancelled, it's `NO`, otherwise it's `YES`;
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) void (^didDisplay)(CALayer *layer, BOOL finished);
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,234 @@
|
|||
//
|
||||
// YYTextAsyncLayer.m
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/4/11.
|
||||
// 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 "YYTextAsyncLayer.h"
|
||||
#import <libkern/OSAtomic.h>
|
||||
|
||||
|
||||
/// Global display queue, used for content rendering.
|
||||
static dispatch_queue_t YYTextAsyncLayerGetDisplayQueue() {
|
||||
#define MAX_QUEUE_COUNT 16
|
||||
static int queueCount;
|
||||
static dispatch_queue_t queues[MAX_QUEUE_COUNT];
|
||||
static dispatch_once_t onceToken;
|
||||
static int32_t counter = 0;
|
||||
dispatch_once(&onceToken, ^{
|
||||
queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount;
|
||||
queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;
|
||||
if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) {
|
||||
for (NSUInteger i = 0; i < queueCount; i++) {
|
||||
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);
|
||||
queues[i] = dispatch_queue_create("com.ibireme.text.render", attr);
|
||||
}
|
||||
} else {
|
||||
for (NSUInteger i = 0; i < queueCount; i++) {
|
||||
queues[i] = dispatch_queue_create("com.ibireme.text.render", DISPATCH_QUEUE_SERIAL);
|
||||
dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
|
||||
}
|
||||
}
|
||||
});
|
||||
uint32_t cur = (uint32_t)OSAtomicIncrement32(&counter);
|
||||
return queues[(cur) % queueCount];
|
||||
#undef MAX_QUEUE_COUNT
|
||||
}
|
||||
|
||||
static dispatch_queue_t YYTextAsyncLayerGetReleaseQueue() {
|
||||
#ifdef YYDispatchQueuePool_h
|
||||
return YYDispatchQueueGetForQOS(NSQualityOfServiceDefault);
|
||||
#else
|
||||
return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/// a thread safe incrementing counter.
|
||||
@interface _YYTextSentinel : NSObject
|
||||
/// Returns the current value of the counter.
|
||||
@property (atomic, readonly) int32_t value;
|
||||
/// Increase the value atomically. @return The new value.
|
||||
- (int32_t)increase;
|
||||
@end
|
||||
|
||||
@implementation _YYTextSentinel {
|
||||
int32_t _value;
|
||||
}
|
||||
- (int32_t)value {
|
||||
return _value;
|
||||
}
|
||||
- (int32_t)increase {
|
||||
return OSAtomicIncrement32(&_value);
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
@implementation YYTextAsyncLayerDisplayTask
|
||||
@end
|
||||
|
||||
|
||||
@implementation YYTextAsyncLayer {
|
||||
_YYTextSentinel *_sentinel;
|
||||
}
|
||||
|
||||
#pragma mark - Override
|
||||
|
||||
+ (id)defaultValueForKey:(NSString *)key {
|
||||
if ([key isEqualToString:@"displaysAsynchronously"]) {
|
||||
return @(YES);
|
||||
} else {
|
||||
return [super defaultValueForKey:key];
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
static CGFloat scale; //global
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
scale = [UIScreen mainScreen].scale;
|
||||
});
|
||||
self.contentsScale = scale;
|
||||
_sentinel = [_YYTextSentinel new];
|
||||
_displaysAsynchronously = YES;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_sentinel increase];
|
||||
}
|
||||
|
||||
- (void)setNeedsDisplay {
|
||||
[self _cancelAsyncDisplay];
|
||||
[super setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)display {
|
||||
super.contents = super.contents;
|
||||
[self _displayAsync:_displaysAsynchronously];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)_displayAsync:(BOOL)async {
|
||||
__strong id<YYTextAsyncLayerDelegate> delegate = (id)self.delegate;
|
||||
YYTextAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask];
|
||||
if (!task.display) {
|
||||
if (task.willDisplay) task.willDisplay(self);
|
||||
self.contents = nil;
|
||||
if (task.didDisplay) task.didDisplay(self, YES);
|
||||
return;
|
||||
}
|
||||
|
||||
if (async) {
|
||||
if (task.willDisplay) task.willDisplay(self);
|
||||
_YYTextSentinel *sentinel = _sentinel;
|
||||
int32_t value = sentinel.value;
|
||||
BOOL (^isCancelled)() = ^BOOL() {
|
||||
return value != sentinel.value;
|
||||
};
|
||||
CGSize size = self.bounds.size;
|
||||
BOOL opaque = self.opaque;
|
||||
CGFloat scale = self.contentsScale;
|
||||
CGColorRef backgroundColor = (opaque && self.backgroundColor) ? CGColorRetain(self.backgroundColor) : NULL;
|
||||
if (size.width < 1 || size.height < 1) {
|
||||
CGImageRef image = (__bridge_retained CGImageRef)(self.contents);
|
||||
self.contents = nil;
|
||||
if (image) {
|
||||
dispatch_async(YYTextAsyncLayerGetReleaseQueue(), ^{
|
||||
CFRelease(image);
|
||||
});
|
||||
}
|
||||
if (task.didDisplay) task.didDisplay(self, YES);
|
||||
CGColorRelease(backgroundColor);
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_async(YYTextAsyncLayerGetDisplayQueue(), ^{
|
||||
if (isCancelled()) {
|
||||
CGColorRelease(backgroundColor);
|
||||
return;
|
||||
}
|
||||
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
if (opaque) {
|
||||
CGContextSaveGState(context); {
|
||||
if (!backgroundColor || CGColorGetAlpha(backgroundColor) < 1) {
|
||||
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
|
||||
CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
|
||||
CGContextFillPath(context);
|
||||
}
|
||||
if (backgroundColor) {
|
||||
CGContextSetFillColorWithColor(context, backgroundColor);
|
||||
CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
|
||||
CGContextFillPath(context);
|
||||
}
|
||||
} CGContextRestoreGState(context);
|
||||
CGColorRelease(backgroundColor);
|
||||
}
|
||||
task.display(context, size, isCancelled);
|
||||
if (isCancelled()) {
|
||||
UIGraphicsEndImageContext();
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (task.didDisplay) task.didDisplay(self, NO);
|
||||
});
|
||||
return;
|
||||
}
|
||||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
if (isCancelled()) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (task.didDisplay) task.didDisplay(self, NO);
|
||||
});
|
||||
return;
|
||||
}
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (isCancelled()) {
|
||||
if (task.didDisplay) task.didDisplay(self, NO);
|
||||
} else {
|
||||
self.contents = (__bridge id)(image.CGImage);
|
||||
if (task.didDisplay) task.didDisplay(self, YES);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
[_sentinel increase];
|
||||
if (task.willDisplay) task.willDisplay(self);
|
||||
UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.opaque, self.contentsScale);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
if (self.opaque) {
|
||||
CGSize size = self.bounds.size;
|
||||
size.width *= self.contentsScale;
|
||||
size.height *= self.contentsScale;
|
||||
CGContextSaveGState(context); {
|
||||
if (!self.backgroundColor || CGColorGetAlpha(self.backgroundColor) < 1) {
|
||||
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
|
||||
CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height));
|
||||
CGContextFillPath(context);
|
||||
}
|
||||
if (self.backgroundColor) {
|
||||
CGContextSetFillColorWithColor(context, self.backgroundColor);
|
||||
CGContextAddRect(context, CGRectMake(0, 0, size.width, size.height));
|
||||
CGContextFillPath(context);
|
||||
}
|
||||
} CGContextRestoreGState(context);
|
||||
}
|
||||
task.display(context, self.bounds.size, ^{return NO;});
|
||||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
self.contents = (__bridge id)(image.CGImage);
|
||||
if (task.didDisplay) task.didDisplay(self, YES);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_cancelAsyncDisplay {
|
||||
[_sentinel increase];
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// YYTextTransaction.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/4/18.
|
||||
// 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>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
YYTextTransaction let you perform a selector once before current runloop sleep.
|
||||
*/
|
||||
@interface YYTextTransaction : NSObject
|
||||
|
||||
/**
|
||||
Creates and returns a transaction with a specified target and selector.
|
||||
|
||||
@param target A specified target, the target is retained until runloop end.
|
||||
@param selector A selector for target.
|
||||
|
||||
@return A new transaction, or nil if an error occurs.
|
||||
*/
|
||||
+ (YYTextTransaction *)transactionWithTarget:(id)target selector:(SEL)selector;
|
||||
|
||||
/**
|
||||
Commit the trancaction to main runloop.
|
||||
|
||||
@discussion It will perform the selector on the target once before main runloop's
|
||||
current loop sleep. If the same transaction (same target and same selector) has
|
||||
already commit to runloop in this loop, this method do nothing.
|
||||
*/
|
||||
- (void)commit;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,81 @@
|
|||
//
|
||||
// YYTextTransaction.m
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/4/18.
|
||||
// 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 "YYTextTransaction.h"
|
||||
|
||||
|
||||
@interface YYTextTransaction()
|
||||
@property (nonatomic, strong) id target;
|
||||
@property (nonatomic, assign) SEL selector;
|
||||
@end
|
||||
|
||||
static NSMutableSet *transactionSet = nil;
|
||||
|
||||
static void YYRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
|
||||
if (transactionSet.count == 0) return;
|
||||
NSSet *currentSet = transactionSet;
|
||||
transactionSet = [NSMutableSet new];
|
||||
[currentSet enumerateObjectsUsingBlock:^(YYTextTransaction *transaction, BOOL *stop) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
[transaction.target performSelector:transaction.selector];
|
||||
#pragma clang diagnostic pop
|
||||
}];
|
||||
}
|
||||
|
||||
static void YYTextTransactionSetup() {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
transactionSet = [NSMutableSet new];
|
||||
CFRunLoopRef runloop = CFRunLoopGetMain();
|
||||
CFRunLoopObserverRef observer;
|
||||
|
||||
observer = CFRunLoopObserverCreate(CFAllocatorGetDefault(),
|
||||
kCFRunLoopBeforeWaiting | kCFRunLoopExit,
|
||||
true, // repeat
|
||||
0xFFFFFF, // after CATransaction(2000000)
|
||||
YYRunLoopObserverCallBack, NULL);
|
||||
CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
|
||||
CFRelease(observer);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@implementation YYTextTransaction
|
||||
|
||||
+ (YYTextTransaction *)transactionWithTarget:(id)target selector:(SEL)selector{
|
||||
if (!target || !selector) return nil;
|
||||
YYTextTransaction *t = [YYTextTransaction new];
|
||||
t.target = target;
|
||||
t.selector = selector;
|
||||
return t;
|
||||
}
|
||||
|
||||
- (void)commit {
|
||||
if (!_target || !_selector) return;
|
||||
YYTextTransactionSetup();
|
||||
[transactionSet addObject:self];
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
long v1 = (long)((void *)_selector);
|
||||
long v2 = (long)_target;
|
||||
return v1 ^ v2;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
if (self == object) return YES;
|
||||
if (![object isMemberOfClass:self.class]) return NO;
|
||||
YYTextTransaction *other = object;
|
||||
return other.selector == _selector && other.target == _target;
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,563 @@
|
|||
//
|
||||
// YYTextUtilities.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/4/6.
|
||||
// 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>
|
||||
#import <CoreText/CoreText.h>
|
||||
|
||||
|
||||
#ifndef YYTEXT_CLAMP // return the clamped value
|
||||
#define YYTEXT_CLAMP(_x_, _low_, _high_) (((_x_) > (_high_)) ? (_high_) : (((_x_) < (_low_)) ? (_low_) : (_x_)))
|
||||
#endif
|
||||
|
||||
#ifndef YYTEXT_SWAP // swap two value
|
||||
#define YYTEXT_SWAP(_a_, _b_) do { __typeof__(_a_) _tmp_ = (_a_); (_a_) = (_b_); (_b_) = _tmp_; } while (0)
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
Whether the character is 'line break char':
|
||||
U+000D (\\r or CR)
|
||||
U+2028 (Unicode line separator)
|
||||
U+000A (\\n or LF)
|
||||
U+2029 (Unicode paragraph separator)
|
||||
|
||||
@param c A character
|
||||
@return YES or NO.
|
||||
*/
|
||||
static inline BOOL YYTextIsLinebreakChar(unichar c) {
|
||||
switch (c) {
|
||||
case 0x000D:
|
||||
case 0x2028:
|
||||
case 0x000A:
|
||||
case 0x2029:
|
||||
return YES;
|
||||
default:
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Whether the string is a 'line break':
|
||||
U+000D (\\r or CR)
|
||||
U+2028 (Unicode line separator)
|
||||
U+000A (\\n or LF)
|
||||
U+2029 (Unicode paragraph separator)
|
||||
\\r\\n, in that order (also known as CRLF)
|
||||
|
||||
@param str A string
|
||||
@return YES or NO.
|
||||
*/
|
||||
static inline BOOL YYTextIsLinebreakString(NSString * _Nullable str) {
|
||||
if (str.length > 2 || str.length == 0) return NO;
|
||||
if (str.length == 1) {
|
||||
unichar c = [str characterAtIndex:0];
|
||||
return YYTextIsLinebreakChar(c);
|
||||
} else {
|
||||
return ([str characterAtIndex:0] == '\r') && ([str characterAtIndex:1] == '\n');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
If the string has a 'line break' suffix, return the 'line break' length.
|
||||
|
||||
@param str A string.
|
||||
@return The length of the tail line break: 0, 1 or 2.
|
||||
*/
|
||||
static inline NSUInteger YYTextLinebreakTailLength(NSString * _Nullable str) {
|
||||
if (str.length >= 2) {
|
||||
unichar c2 = [str characterAtIndex:str.length - 1];
|
||||
if (YYTextIsLinebreakChar(c2)) {
|
||||
unichar c1 = [str characterAtIndex:str.length - 2];
|
||||
if (c1 == '\r' && c2 == '\n') return 2;
|
||||
else return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else if (str.length == 1) {
|
||||
return YYTextIsLinebreakChar([str characterAtIndex:0]) ? 1 : 0;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Convert `UIDataDetectorTypes` to `NSTextCheckingType`.
|
||||
|
||||
@param types The `UIDataDetectorTypes` type.
|
||||
@return The `NSTextCheckingType` type.
|
||||
*/
|
||||
static inline NSTextCheckingType YYTextNSTextCheckingTypeFromUIDataDetectorType(UIDataDetectorTypes types) {
|
||||
NSTextCheckingType t = 0;
|
||||
if (types & UIDataDetectorTypePhoneNumber) t |= NSTextCheckingTypePhoneNumber;
|
||||
if (types & UIDataDetectorTypeLink) t |= NSTextCheckingTypeLink;
|
||||
if (types & UIDataDetectorTypeAddress) t |= NSTextCheckingTypeAddress;
|
||||
if (types & UIDataDetectorTypeCalendarEvent) t |= NSTextCheckingTypeDate;
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
Whether the font is `AppleColorEmoji` font.
|
||||
|
||||
@param font A font.
|
||||
@return YES: the font is Emoji, NO: the font is not Emoji.
|
||||
*/
|
||||
static inline BOOL YYTextUIFontIsEmoji(UIFont *font) {
|
||||
return [font.fontName isEqualToString:@"AppleColorEmoji"];
|
||||
}
|
||||
|
||||
/**
|
||||
Whether the font is `AppleColorEmoji` font.
|
||||
|
||||
@param font A font.
|
||||
@return YES: the font is Emoji, NO: the font is not Emoji.
|
||||
*/
|
||||
static inline BOOL YYTextCTFontIsEmoji(CTFontRef font) {
|
||||
BOOL isEmoji = NO;
|
||||
CFStringRef name = CTFontCopyPostScriptName(font);
|
||||
if (name && CFEqual(CFSTR("AppleColorEmoji"), name)) isEmoji = YES;
|
||||
if (name) CFRelease(name);
|
||||
return isEmoji;
|
||||
}
|
||||
|
||||
/**
|
||||
Whether the font is `AppleColorEmoji` font.
|
||||
|
||||
@param font A font.
|
||||
@return YES: the font is Emoji, NO: the font is not Emoji.
|
||||
*/
|
||||
static inline BOOL YYTextCGFontIsEmoji(CGFontRef font) {
|
||||
BOOL isEmoji = NO;
|
||||
CFStringRef name = CGFontCopyPostScriptName(font);
|
||||
if (name && CFEqual(CFSTR("AppleColorEmoji"), name)) isEmoji = YES;
|
||||
if (name) CFRelease(name);
|
||||
return isEmoji;
|
||||
}
|
||||
|
||||
/**
|
||||
Whether the font contains color bitmap glyphs.
|
||||
|
||||
@discussion Only `AppleColorEmoji` contains color bitmap glyphs in iOS system fonts.
|
||||
@param font A font.
|
||||
@return YES: the font contains color bitmap glyphs, NO: the font has no color bitmap glyph.
|
||||
*/
|
||||
static inline BOOL YYTextCTFontContainsColorBitmapGlyphs(CTFontRef font) {
|
||||
return (CTFontGetSymbolicTraits(font) & kCTFontTraitColorGlyphs) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
Whether the glyph is bitmap.
|
||||
|
||||
@param font The glyph's font.
|
||||
@param glyph The glyph which is created from the specified font.
|
||||
@return YES: the glyph is bitmap, NO: the glyph is vector.
|
||||
*/
|
||||
static inline BOOL YYTextCGGlyphIsBitmap(CTFontRef font, CGGlyph glyph) {
|
||||
if (!font && !glyph) return NO;
|
||||
if (!YYTextCTFontContainsColorBitmapGlyphs(font)) return NO;
|
||||
CGPathRef path = CTFontCreatePathForGlyph(font, glyph, NULL);
|
||||
if (path) {
|
||||
CFRelease(path);
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
/**
|
||||
Get the `AppleColorEmoji` font's ascent with a specified font size.
|
||||
It may used to create custom emoji.
|
||||
|
||||
@param fontSize The specified font size.
|
||||
@return The font ascent.
|
||||
*/
|
||||
static inline CGFloat YYTextEmojiGetAscentWithFontSize(CGFloat fontSize) {
|
||||
if (fontSize < 16) {
|
||||
return 1.25 * fontSize;
|
||||
} else if (16 <= fontSize && fontSize <= 24) {
|
||||
return 0.5 * fontSize + 12;
|
||||
} else {
|
||||
return fontSize;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Get the `AppleColorEmoji` font's descent with a specified font size.
|
||||
It may used to create custom emoji.
|
||||
|
||||
@param fontSize The specified font size.
|
||||
@return The font descent.
|
||||
*/
|
||||
static inline CGFloat YYTextEmojiGetDescentWithFontSize(CGFloat fontSize) {
|
||||
if (fontSize < 16) {
|
||||
return 0.390625 * fontSize;
|
||||
} else if (16 <= fontSize && fontSize <= 24) {
|
||||
return 0.15625 * fontSize + 3.75;
|
||||
} else {
|
||||
return 0.3125 * fontSize;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
Get the `AppleColorEmoji` font's glyph bounding rect with a specified font size.
|
||||
It may used to create custom emoji.
|
||||
|
||||
@param fontSize The specified font size.
|
||||
@return The font glyph bounding rect.
|
||||
*/
|
||||
static inline CGRect YYTextEmojiGetGlyphBoundingRectWithFontSize(CGFloat fontSize) {
|
||||
CGRect rect;
|
||||
rect.origin.x = 0.75;
|
||||
rect.size.width = rect.size.height = YYTextEmojiGetAscentWithFontSize(fontSize);
|
||||
if (fontSize < 16) {
|
||||
rect.origin.y = -0.2525 * fontSize;
|
||||
} else if (16 <= fontSize && fontSize <= 24) {
|
||||
rect.origin.y = 0.1225 * fontSize -6;
|
||||
} else {
|
||||
rect.origin.y = -0.1275 * fontSize;
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Get the character set which should rotate in vertical form.
|
||||
@return The shared character set.
|
||||
*/
|
||||
NSCharacterSet *YYTextVerticalFormRotateCharacterSet();
|
||||
|
||||
/**
|
||||
Get the character set which should rotate and move in vertical form.
|
||||
@return The shared character set.
|
||||
*/
|
||||
NSCharacterSet *YYTextVerticalFormRotateAndMoveCharacterSet();
|
||||
|
||||
|
||||
|
||||
/// Convert degrees to radians.
|
||||
static inline CGFloat YYTextDegreesToRadians(CGFloat degrees) {
|
||||
return degrees * M_PI / 180;
|
||||
}
|
||||
|
||||
/// Convert radians to degrees.
|
||||
static inline CGFloat YYTextRadiansToDegrees(CGFloat radians) {
|
||||
return radians * 180 / M_PI;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Get the transform rotation.
|
||||
/// @return the rotation in radians [-PI,PI] ([-180°,180°])
|
||||
static inline CGFloat YYTextCGAffineTransformGetRotation(CGAffineTransform transform) {
|
||||
return atan2(transform.b, transform.a);
|
||||
}
|
||||
|
||||
/// Get the transform's scale.x
|
||||
static inline CGFloat YYTextCGAffineTransformGetScaleX(CGAffineTransform transform) {
|
||||
return sqrt(transform.a * transform.a + transform.c * transform.c);
|
||||
}
|
||||
|
||||
/// Get the transform's scale.y
|
||||
static inline CGFloat YYTextCGAffineTransformGetScaleY(CGAffineTransform transform) {
|
||||
return sqrt(transform.b * transform.b + transform.d * transform.d);
|
||||
}
|
||||
|
||||
/// Get the transform's translate.x
|
||||
static inline CGFloat YYTextCGAffineTransformGetTranslateX(CGAffineTransform transform) {
|
||||
return transform.tx;
|
||||
}
|
||||
|
||||
/// Get the transform's translate.y
|
||||
static inline CGFloat YYTextCGAffineTransformGetTranslateY(CGAffineTransform transform) {
|
||||
return transform.ty;
|
||||
}
|
||||
|
||||
/**
|
||||
If you have 3 pair of points transformed by a same CGAffineTransform:
|
||||
p1 (transform->) q1
|
||||
p2 (transform->) q2
|
||||
p3 (transform->) q3
|
||||
This method returns the original transform matrix from these 3 pair of points.
|
||||
|
||||
@see http://stackoverflow.com/questions/13291796/calculate-values-for-a-cgaffinetransform-from-three-points-in-each-of-two-uiview
|
||||
*/
|
||||
CGAffineTransform YYTextCGAffineTransformGetFromPoints(CGPoint before[3], CGPoint after[3]);
|
||||
|
||||
/// Get the transform which can converts a point from the coordinate system of a given view to another.
|
||||
CGAffineTransform YYTextCGAffineTransformGetFromViews(UIView *from, UIView *to);
|
||||
|
||||
/// Create a skew transform.
|
||||
static inline CGAffineTransform YYTextCGAffineTransformMakeSkew(CGFloat x, CGFloat y){
|
||||
CGAffineTransform transform = CGAffineTransformIdentity;
|
||||
transform.c = -x;
|
||||
transform.b = y;
|
||||
return transform;
|
||||
}
|
||||
|
||||
/// Negates/inverts a UIEdgeInsets.
|
||||
static inline UIEdgeInsets YYTextUIEdgeInsetsInvert(UIEdgeInsets insets) {
|
||||
return UIEdgeInsetsMake(-insets.top, -insets.left, -insets.bottom, -insets.right);
|
||||
}
|
||||
|
||||
/// Convert CALayer's gravity string to UIViewContentMode.
|
||||
UIViewContentMode YYTextCAGravityToUIViewContentMode(NSString *gravity);
|
||||
|
||||
/// Convert UIViewContentMode to CALayer's gravity string.
|
||||
NSString *YYTextUIViewContentModeToCAGravity(UIViewContentMode contentMode);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Returns a rectangle to fit the @param rect with specified content mode.
|
||||
|
||||
@param rect The constrant rect
|
||||
@param size The content size
|
||||
@param mode The content mode
|
||||
@return A rectangle for the given content mode.
|
||||
@discussion UIViewContentModeRedraw is same as UIViewContentModeScaleToFill.
|
||||
*/
|
||||
CGRect YYTextCGRectFitWithContentMode(CGRect rect, CGSize size, UIViewContentMode mode);
|
||||
|
||||
/// Returns the center for the rectangle.
|
||||
static inline CGPoint YYTextCGRectGetCenter(CGRect rect) {
|
||||
return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
|
||||
}
|
||||
|
||||
/// Returns the area of the rectangle.
|
||||
static inline CGFloat YYTextCGRectGetArea(CGRect rect) {
|
||||
if (CGRectIsNull(rect)) return 0;
|
||||
rect = CGRectStandardize(rect);
|
||||
return rect.size.width * rect.size.height;
|
||||
}
|
||||
|
||||
/// Returns the distance between two points.
|
||||
static inline CGFloat YYTextCGPointGetDistanceToPoint(CGPoint p1, CGPoint p2) {
|
||||
return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
|
||||
}
|
||||
|
||||
/// Returns the minmium distance between a point to a rectangle.
|
||||
static inline CGFloat YYTextCGPointGetDistanceToRect(CGPoint p, CGRect r) {
|
||||
r = CGRectStandardize(r);
|
||||
if (CGRectContainsPoint(r, p)) return 0;
|
||||
CGFloat distV, distH;
|
||||
if (CGRectGetMinY(r) <= p.y && p.y <= CGRectGetMaxY(r)) {
|
||||
distV = 0;
|
||||
} else {
|
||||
distV = p.y < CGRectGetMinY(r) ? CGRectGetMinY(r) - p.y : p.y - CGRectGetMaxY(r);
|
||||
}
|
||||
if (CGRectGetMinX(r) <= p.x && p.x <= CGRectGetMaxX(r)) {
|
||||
distH = 0;
|
||||
} else {
|
||||
distH = p.x < CGRectGetMinX(r) ? CGRectGetMinX(r) - p.x : p.x - CGRectGetMaxX(r);
|
||||
}
|
||||
return MAX(distV, distH);
|
||||
}
|
||||
|
||||
|
||||
/// Get main screen's scale.
|
||||
CGFloat YYTextScreenScale();
|
||||
|
||||
/// Get main screen's size. Height is always larger than width.
|
||||
CGSize YYTextScreenSize();
|
||||
|
||||
/// Convert point to pixel.
|
||||
static inline CGFloat YYTextCGFloatToPixel(CGFloat value) {
|
||||
return value * YYTextScreenScale();
|
||||
}
|
||||
|
||||
/// Convert pixel to point.
|
||||
static inline CGFloat YYTextCGFloatFromPixel(CGFloat value) {
|
||||
return value / YYTextScreenScale();
|
||||
}
|
||||
|
||||
/// floor point value for pixel-aligned
|
||||
static inline CGFloat YYTextCGFloatPixelFloor(CGFloat value) {
|
||||
CGFloat scale = YYTextScreenScale();
|
||||
return floor(value * scale) / scale;
|
||||
}
|
||||
|
||||
/// round point value for pixel-aligned
|
||||
static inline CGFloat YYTextCGFloatPixelRound(CGFloat value) {
|
||||
CGFloat scale = YYTextScreenScale();
|
||||
return round(value * scale) / scale;
|
||||
}
|
||||
|
||||
/// ceil point value for pixel-aligned
|
||||
static inline CGFloat YYTextCGFloatPixelCeil(CGFloat value) {
|
||||
CGFloat scale = YYTextScreenScale();
|
||||
return ceil(value * scale) / scale;
|
||||
}
|
||||
|
||||
/// round point value to .5 pixel for path stroke (odd pixel line width pixel-aligned)
|
||||
static inline CGFloat YYTextCGFloatPixelHalf(CGFloat value) {
|
||||
CGFloat scale = YYTextScreenScale();
|
||||
return (floor(value * scale) + 0.5) / scale;
|
||||
}
|
||||
|
||||
/// floor point value for pixel-aligned
|
||||
static inline CGPoint YYTextCGPointPixelFloor(CGPoint point) {
|
||||
CGFloat scale = YYTextScreenScale();
|
||||
return CGPointMake(floor(point.x * scale) / scale,
|
||||
floor(point.y * scale) / scale);
|
||||
}
|
||||
|
||||
/// round point value for pixel-aligned
|
||||
static inline CGPoint YYTextCGPointPixelRound(CGPoint point) {
|
||||
CGFloat scale = YYTextScreenScale();
|
||||
return CGPointMake(round(point.x * scale) / scale,
|
||||
round(point.y * scale) / scale);
|
||||
}
|
||||
|
||||
/// ceil point value for pixel-aligned
|
||||
static inline CGPoint YYTextCGPointPixelCeil(CGPoint point) {
|
||||
CGFloat scale = YYTextScreenScale();
|
||||
return CGPointMake(ceil(point.x * scale) / scale,
|
||||
ceil(point.y * scale) / scale);
|
||||
}
|
||||
|
||||
/// round point value to .5 pixel for path stroke (odd pixel line width pixel-aligned)
|
||||
static inline CGPoint YYTextCGPointPixelHalf(CGPoint point) {
|
||||
CGFloat scale = YYTextScreenScale();
|
||||
return CGPointMake((floor(point.x * scale) + 0.5) / scale,
|
||||
(floor(point.y * scale) + 0.5) / scale);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// floor point value for pixel-aligned
|
||||
static inline CGSize YYTextCGSizePixelFloor(CGSize size) {
|
||||
CGFloat scale = YYTextScreenScale();
|
||||
return CGSizeMake(floor(size.width * scale) / scale,
|
||||
floor(size.height * scale) / scale);
|
||||
}
|
||||
|
||||
/// round point value for pixel-aligned
|
||||
static inline CGSize YYTextCGSizePixelRound(CGSize size) {
|
||||
CGFloat scale = YYTextScreenScale();
|
||||
return CGSizeMake(round(size.width * scale) / scale,
|
||||
round(size.height * scale) / scale);
|
||||
}
|
||||
|
||||
/// ceil point value for pixel-aligned
|
||||
static inline CGSize YYTextCGSizePixelCeil(CGSize size) {
|
||||
CGFloat scale = YYTextScreenScale();
|
||||
return CGSizeMake(ceil(size.width * scale) / scale,
|
||||
ceil(size.height * scale) / scale);
|
||||
}
|
||||
|
||||
/// round point value to .5 pixel for path stroke (odd pixel line width pixel-aligned)
|
||||
static inline CGSize YYTextCGSizePixelHalf(CGSize size) {
|
||||
CGFloat scale = YYTextScreenScale();
|
||||
return CGSizeMake((floor(size.width * scale) + 0.5) / scale,
|
||||
(floor(size.height * scale) + 0.5) / scale);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// floor point value for pixel-aligned
|
||||
static inline CGRect YYTextCGRectPixelFloor(CGRect rect) {
|
||||
CGPoint origin = YYTextCGPointPixelCeil(rect.origin);
|
||||
CGPoint corner = YYTextCGPointPixelFloor(CGPointMake(rect.origin.x + rect.size.width,
|
||||
rect.origin.y + rect.size.height));
|
||||
CGRect ret = CGRectMake(origin.x, origin.y, corner.x - origin.x, corner.y - origin.y);
|
||||
if (ret.size.width < 0) ret.size.width = 0;
|
||||
if (ret.size.height < 0) ret.size.height = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// round point value for pixel-aligned
|
||||
static inline CGRect YYTextCGRectPixelRound(CGRect rect) {
|
||||
CGPoint origin = YYTextCGPointPixelRound(rect.origin);
|
||||
CGPoint corner = YYTextCGPointPixelRound(CGPointMake(rect.origin.x + rect.size.width,
|
||||
rect.origin.y + rect.size.height));
|
||||
return CGRectMake(origin.x, origin.y, corner.x - origin.x, corner.y - origin.y);
|
||||
}
|
||||
|
||||
/// ceil point value for pixel-aligned
|
||||
static inline CGRect YYTextCGRectPixelCeil(CGRect rect) {
|
||||
CGPoint origin = YYTextCGPointPixelFloor(rect.origin);
|
||||
CGPoint corner = YYTextCGPointPixelCeil(CGPointMake(rect.origin.x + rect.size.width,
|
||||
rect.origin.y + rect.size.height));
|
||||
return CGRectMake(origin.x, origin.y, corner.x - origin.x, corner.y - origin.y);
|
||||
}
|
||||
|
||||
/// round point value to .5 pixel for path stroke (odd pixel line width pixel-aligned)
|
||||
static inline CGRect YYTextCGRectPixelHalf(CGRect rect) {
|
||||
CGPoint origin = YYTextCGPointPixelHalf(rect.origin);
|
||||
CGPoint corner = YYTextCGPointPixelHalf(CGPointMake(rect.origin.x + rect.size.width,
|
||||
rect.origin.y + rect.size.height));
|
||||
return CGRectMake(origin.x, origin.y, corner.x - origin.x, corner.y - origin.y);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// floor UIEdgeInset for pixel-aligned
|
||||
static inline UIEdgeInsets YYTextUIEdgeInsetPixelFloor(UIEdgeInsets insets) {
|
||||
insets.top = YYTextCGFloatPixelFloor(insets.top);
|
||||
insets.left = YYTextCGFloatPixelFloor(insets.left);
|
||||
insets.bottom = YYTextCGFloatPixelFloor(insets.bottom);
|
||||
insets.right = YYTextCGFloatPixelFloor(insets.right);
|
||||
return insets;
|
||||
}
|
||||
|
||||
/// ceil UIEdgeInset for pixel-aligned
|
||||
static inline UIEdgeInsets YYTextUIEdgeInsetPixelCeil(UIEdgeInsets insets) {
|
||||
insets.top = YYTextCGFloatPixelCeil(insets.top);
|
||||
insets.left = YYTextCGFloatPixelCeil(insets.left);
|
||||
insets.bottom = YYTextCGFloatPixelCeil(insets.bottom);
|
||||
insets.right = YYTextCGFloatPixelCeil(insets.right);
|
||||
return insets;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static inline UIFont * _Nullable YYTextFontWithBold(UIFont *font) {
|
||||
if (![font respondsToSelector:@selector(fontDescriptor)]) return font;
|
||||
return [UIFont fontWithDescriptor:[font.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold] size:font.pointSize];
|
||||
}
|
||||
|
||||
static inline UIFont * _Nullable YYTextFontWithItalic(UIFont *font) {
|
||||
if (![font respondsToSelector:@selector(fontDescriptor)]) return font;
|
||||
return [UIFont fontWithDescriptor:[font.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitItalic] size:font.pointSize];
|
||||
}
|
||||
|
||||
static inline UIFont * _Nullable YYTextFontWithBoldItalic(UIFont *font) {
|
||||
if (![font respondsToSelector:@selector(fontDescriptor)]) return font;
|
||||
return [UIFont fontWithDescriptor:[font.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold | UIFontDescriptorTraitItalic] size:font.pointSize];
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Convert CFRange to NSRange
|
||||
@param range CFRange @return NSRange
|
||||
*/
|
||||
static inline NSRange YYTextNSRangeFromCFRange(CFRange range) {
|
||||
return NSMakeRange(range.location, range.length);
|
||||
}
|
||||
|
||||
/**
|
||||
Convert NSRange to CFRange
|
||||
@param range NSRange @return CFRange
|
||||
*/
|
||||
static inline CFRange YYTextCFRangeFromNSRange(NSRange range) {
|
||||
return CFRangeMake(range.location, range.length);
|
||||
}
|
||||
|
||||
|
||||
/// Returns YES in App Extension.
|
||||
BOOL YYTextIsAppExtension();
|
||||
|
||||
/// Returns nil in App Extension.
|
||||
UIApplication * _Nullable YYTextSharedApplication();
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,309 @@
|
|||
//
|
||||
// YYTextUtilities.m
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/4/6.
|
||||
// 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 "YYTextUtilities.h"
|
||||
#import <Accelerate/Accelerate.h>
|
||||
#import "UIView+YYText.h"
|
||||
|
||||
NSCharacterSet *YYTextVerticalFormRotateCharacterSet() {
|
||||
static NSMutableCharacterSet *set;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
set = [NSMutableCharacterSet new];
|
||||
[set addCharactersInRange:NSMakeRange(0x1100, 256)]; // Hangul Jamo
|
||||
[set addCharactersInRange:NSMakeRange(0x2460, 160)]; // Enclosed Alphanumerics
|
||||
[set addCharactersInRange:NSMakeRange(0x2600, 256)]; // Miscellaneous Symbols
|
||||
[set addCharactersInRange:NSMakeRange(0x2700, 192)]; // Dingbats
|
||||
[set addCharactersInRange:NSMakeRange(0x2E80, 128)]; // CJK Radicals Supplement
|
||||
[set addCharactersInRange:NSMakeRange(0x2F00, 224)]; // Kangxi Radicals
|
||||
[set addCharactersInRange:NSMakeRange(0x2FF0, 16)]; // Ideographic Description Characters
|
||||
[set addCharactersInRange:NSMakeRange(0x3000, 64)]; // CJK Symbols and Punctuation
|
||||
[set removeCharactersInRange:NSMakeRange(0x3008, 10)];
|
||||
[set removeCharactersInRange:NSMakeRange(0x3014, 12)];
|
||||
[set addCharactersInRange:NSMakeRange(0x3040, 96)]; // Hiragana
|
||||
[set addCharactersInRange:NSMakeRange(0x30A0, 96)]; // Katakana
|
||||
[set addCharactersInRange:NSMakeRange(0x3100, 48)]; // Bopomofo
|
||||
[set addCharactersInRange:NSMakeRange(0x3130, 96)]; // Hangul Compatibility Jamo
|
||||
[set addCharactersInRange:NSMakeRange(0x3190, 16)]; // Kanbun
|
||||
[set addCharactersInRange:NSMakeRange(0x31A0, 32)]; // Bopomofo Extended
|
||||
[set addCharactersInRange:NSMakeRange(0x31C0, 48)]; // CJK Strokes
|
||||
[set addCharactersInRange:NSMakeRange(0x31F0, 16)]; // Katakana Phonetic Extensions
|
||||
[set addCharactersInRange:NSMakeRange(0x3200, 256)]; // Enclosed CJK Letters and Months
|
||||
[set addCharactersInRange:NSMakeRange(0x3300, 256)]; // CJK Compatibility
|
||||
[set addCharactersInRange:NSMakeRange(0x3400, 2582)]; // CJK Unified Ideographs Extension A
|
||||
[set addCharactersInRange:NSMakeRange(0x4E00, 20941)]; // CJK Unified Ideographs
|
||||
[set addCharactersInRange:NSMakeRange(0xAC00, 11172)]; // Hangul Syllables
|
||||
[set addCharactersInRange:NSMakeRange(0xD7B0, 80)]; // Hangul Jamo Extended-B
|
||||
[set addCharactersInString:@""]; // U+F8FF (Private Use Area)
|
||||
[set addCharactersInRange:NSMakeRange(0xF900, 512)]; // CJK Compatibility Ideographs
|
||||
[set addCharactersInRange:NSMakeRange(0xFE10, 16)]; // Vertical Forms
|
||||
[set addCharactersInRange:NSMakeRange(0xFF00, 240)]; // Halfwidth and Fullwidth Forms
|
||||
[set addCharactersInRange:NSMakeRange(0x1F200, 256)]; // Enclosed Ideographic Supplement
|
||||
[set addCharactersInRange:NSMakeRange(0x1F300, 768)]; // Enclosed Ideographic Supplement
|
||||
[set addCharactersInRange:NSMakeRange(0x1F600, 80)]; // Emoticons (Emoji)
|
||||
[set addCharactersInRange:NSMakeRange(0x1F680, 128)]; // Transport and Map Symbols
|
||||
|
||||
// See http://unicode-table.com/ for more information.
|
||||
});
|
||||
return set;
|
||||
}
|
||||
|
||||
NSCharacterSet *YYTextVerticalFormRotateAndMoveCharacterSet() {
|
||||
static NSMutableCharacterSet *set;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
set = [NSMutableCharacterSet new];
|
||||
[set addCharactersInString:@",。、."];
|
||||
});
|
||||
return set;
|
||||
}
|
||||
|
||||
// return 0 when succeed
|
||||
static int matrix_invert(__CLPK_integer N, double *matrix) {
|
||||
__CLPK_integer error = 0;
|
||||
__CLPK_integer pivot_tmp[6 * 6];
|
||||
__CLPK_integer *pivot = pivot_tmp;
|
||||
double workspace_tmp[6 * 6];
|
||||
double *workspace = workspace_tmp;
|
||||
bool need_free = false;
|
||||
|
||||
if (N > 6) {
|
||||
need_free = true;
|
||||
pivot = malloc(N * N * sizeof(__CLPK_integer));
|
||||
if (!pivot) return -1;
|
||||
workspace = malloc(N * sizeof(double));
|
||||
if (!workspace) {
|
||||
free(pivot);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
dgetrf_(&N, &N, matrix, &N, pivot, &error);
|
||||
|
||||
if (error == 0) {
|
||||
dgetri_(&N, matrix, &N, pivot, workspace, &N, &error);
|
||||
}
|
||||
|
||||
if (need_free) {
|
||||
free(pivot);
|
||||
free(workspace);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
CGAffineTransform YYTextCGAffineTransformGetFromPoints(CGPoint before[3], CGPoint after[3]) {
|
||||
if (before == NULL || after == NULL) return CGAffineTransformIdentity;
|
||||
|
||||
CGPoint p1, p2, p3, q1, q2, q3;
|
||||
p1 = before[0]; p2 = before[1]; p3 = before[2];
|
||||
q1 = after[0]; q2 = after[1]; q3 = after[2];
|
||||
|
||||
double A[36];
|
||||
A[ 0] = p1.x; A[ 1] = p1.y; A[ 2] = 0; A[ 3] = 0; A[ 4] = 1; A[ 5] = 0;
|
||||
A[ 6] = 0; A[ 7] = 0; A[ 8] = p1.x; A[ 9] = p1.y; A[10] = 0; A[11] = 1;
|
||||
A[12] = p2.x; A[13] = p2.y; A[14] = 0; A[15] = 0; A[16] = 1; A[17] = 0;
|
||||
A[18] = 0; A[19] = 0; A[20] = p2.x; A[21] = p2.y; A[22] = 0; A[23] = 1;
|
||||
A[24] = p3.x; A[25] = p3.y; A[26] = 0; A[27] = 0; A[28] = 1; A[29] = 0;
|
||||
A[30] = 0; A[31] = 0; A[32] = p3.x; A[33] = p3.y; A[34] = 0; A[35] = 1;
|
||||
|
||||
int error = matrix_invert(6, A);
|
||||
if (error) return CGAffineTransformIdentity;
|
||||
|
||||
double B[6];
|
||||
B[0] = q1.x; B[1] = q1.y; B[2] = q2.x; B[3] = q2.y; B[4] = q3.x; B[5] = q3.y;
|
||||
|
||||
double M[6];
|
||||
M[0] = A[ 0] * B[0] + A[ 1] * B[1] + A[ 2] * B[2] + A[ 3] * B[3] + A[ 4] * B[4] + A[ 5] * B[5];
|
||||
M[1] = A[ 6] * B[0] + A[ 7] * B[1] + A[ 8] * B[2] + A[ 9] * B[3] + A[10] * B[4] + A[11] * B[5];
|
||||
M[2] = A[12] * B[0] + A[13] * B[1] + A[14] * B[2] + A[15] * B[3] + A[16] * B[4] + A[17] * B[5];
|
||||
M[3] = A[18] * B[0] + A[19] * B[1] + A[20] * B[2] + A[21] * B[3] + A[22] * B[4] + A[23] * B[5];
|
||||
M[4] = A[24] * B[0] + A[25] * B[1] + A[26] * B[2] + A[27] * B[3] + A[28] * B[4] + A[29] * B[5];
|
||||
M[5] = A[30] * B[0] + A[31] * B[1] + A[32] * B[2] + A[33] * B[3] + A[34] * B[4] + A[35] * B[5];
|
||||
|
||||
CGAffineTransform transform = CGAffineTransformMake(M[0], M[2], M[1], M[3], M[4], M[5]);
|
||||
return transform;
|
||||
}
|
||||
|
||||
CGAffineTransform YYTextCGAffineTransformGetFromViews(UIView *from, UIView *to) {
|
||||
if (!from || !to) return CGAffineTransformIdentity;
|
||||
|
||||
CGPoint before[3], after[3];
|
||||
before[0] = CGPointMake(0, 0);
|
||||
before[1] = CGPointMake(0, 1);
|
||||
before[2] = CGPointMake(1, 0);
|
||||
after[0] = [from yy_convertPoint:before[0] toViewOrWindow:to];
|
||||
after[1] = [from yy_convertPoint:before[1] toViewOrWindow:to];
|
||||
after[2] = [from yy_convertPoint:before[2] toViewOrWindow:to];
|
||||
|
||||
return YYTextCGAffineTransformGetFromPoints(before, after);
|
||||
}
|
||||
|
||||
UIViewContentMode YYTextCAGravityToUIViewContentMode(NSString *gravity) {
|
||||
static NSDictionary *dic;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
dic = @{ kCAGravityCenter:@(UIViewContentModeCenter),
|
||||
kCAGravityTop:@(UIViewContentModeTop),
|
||||
kCAGravityBottom:@(UIViewContentModeBottom),
|
||||
kCAGravityLeft:@(UIViewContentModeLeft),
|
||||
kCAGravityRight:@(UIViewContentModeRight),
|
||||
kCAGravityTopLeft:@(UIViewContentModeTopLeft),
|
||||
kCAGravityTopRight:@(UIViewContentModeTopRight),
|
||||
kCAGravityBottomLeft:@(UIViewContentModeBottomLeft),
|
||||
kCAGravityBottomRight:@(UIViewContentModeBottomRight),
|
||||
kCAGravityResize:@(UIViewContentModeScaleToFill),
|
||||
kCAGravityResizeAspect:@(UIViewContentModeScaleAspectFit),
|
||||
kCAGravityResizeAspectFill:@(UIViewContentModeScaleAspectFill) };
|
||||
});
|
||||
if (!gravity) return UIViewContentModeScaleToFill;
|
||||
return (UIViewContentMode)((NSNumber *)dic[gravity]).integerValue;
|
||||
}
|
||||
|
||||
NSString *YYTextUIViewContentModeToCAGravity(UIViewContentMode contentMode) {
|
||||
switch (contentMode) {
|
||||
case UIViewContentModeScaleToFill: return kCAGravityResize;
|
||||
case UIViewContentModeScaleAspectFit: return kCAGravityResizeAspect;
|
||||
case UIViewContentModeScaleAspectFill: return kCAGravityResizeAspectFill;
|
||||
case UIViewContentModeRedraw: return kCAGravityResize;
|
||||
case UIViewContentModeCenter: return kCAGravityCenter;
|
||||
case UIViewContentModeTop: return kCAGravityTop;
|
||||
case UIViewContentModeBottom: return kCAGravityBottom;
|
||||
case UIViewContentModeLeft: return kCAGravityLeft;
|
||||
case UIViewContentModeRight: return kCAGravityRight;
|
||||
case UIViewContentModeTopLeft: return kCAGravityTopLeft;
|
||||
case UIViewContentModeTopRight: return kCAGravityTopRight;
|
||||
case UIViewContentModeBottomLeft: return kCAGravityBottomLeft;
|
||||
case UIViewContentModeBottomRight: return kCAGravityBottomRight;
|
||||
default: return kCAGravityResize;
|
||||
}
|
||||
}
|
||||
|
||||
CGRect YYTextCGRectFitWithContentMode(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 (mode == UIViewContentModeScaleAspectFit) {
|
||||
if (size.width / size.height < rect.size.width / rect.size.height) {
|
||||
scale = rect.size.height / size.height;
|
||||
} else {
|
||||
scale = rect.size.width / size.width;
|
||||
}
|
||||
} else {
|
||||
if (size.width / size.height < rect.size.width / rect.size.height) {
|
||||
scale = rect.size.width / size.width;
|
||||
} else {
|
||||
scale = rect.size.height / size.height;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
CGFloat YYTextScreenScale() {
|
||||
static CGFloat scale;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
scale = [UIScreen mainScreen].scale;
|
||||
});
|
||||
return scale;
|
||||
}
|
||||
|
||||
CGSize YYTextScreenSize() {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
BOOL YYTextIsAppExtension() {
|
||||
static BOOL isAppExtension = NO;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
Class cls = NSClassFromString(@"UIApplication");
|
||||
if(!cls || ![cls respondsToSelector:@selector(sharedApplication)]) isAppExtension = YES;
|
||||
if ([[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]) isAppExtension = YES;
|
||||
});
|
||||
return isAppExtension;
|
||||
}
|
||||
|
||||
UIApplication *YYTextSharedApplication() {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wundeclared-selector"
|
||||
return YYTextIsAppExtension() ? nil : [UIApplication performSelector:@selector(sharedApplication)];
|
||||
#pragma clang diagnostic pop
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// YYTextWeakProxy.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 14/10/18.
|
||||
// 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>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
A proxy used to hold a weak object.
|
||||
It can be used to avoid retain cycles, such as the target in NSTimer or CADisplayLink.
|
||||
|
||||
sample code:
|
||||
|
||||
@implementation MyView {
|
||||
NSTimer *_timer;
|
||||
}
|
||||
|
||||
- (void)initTimer {
|
||||
YYTextWeakProxy *proxy = [YYTextWeakProxy proxyWithTarget:self];
|
||||
_timer = [NSTimer timerWithTimeInterval:0.1 target:proxy selector:@selector(tick:) userInfo:nil repeats:YES];
|
||||
}
|
||||
|
||||
- (void)tick:(NSTimer *)timer {...}
|
||||
@end
|
||||
*/
|
||||
@interface YYTextWeakProxy : NSProxy
|
||||
|
||||
/**
|
||||
The proxy target.
|
||||
*/
|
||||
@property (nullable, nonatomic, weak, readonly) id target;
|
||||
|
||||
/**
|
||||
Creates a new weak proxy for target.
|
||||
|
||||
@param target Target object.
|
||||
|
||||
@return A new proxy object.
|
||||
*/
|
||||
- (instancetype)initWithTarget:(id)target;
|
||||
|
||||
/**
|
||||
Creates a new weak proxy for target.
|
||||
|
||||
@param target Target object.
|
||||
|
||||
@return A new proxy object.
|
||||
*/
|
||||
+ (instancetype)proxyWithTarget:(id)target;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
|
@ -0,0 +1,83 @@
|
|||
//
|
||||
// YYTextWeakProxy.m
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 14/10/18.
|
||||
// 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 "YYTextWeakProxy.h"
|
||||
|
||||
|
||||
@implementation YYTextWeakProxy
|
||||
|
||||
- (instancetype)initWithTarget:(id)target {
|
||||
_target = target;
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype)proxyWithTarget:(id)target {
|
||||
return [[YYTextWeakProxy 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
|
|
@ -0,0 +1,380 @@
|
|||
//
|
||||
// YYLabel.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/2/25.
|
||||
// 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(<YYText/YYText.h>)
|
||||
#import <YYText/YYTextParser.h>
|
||||
#import <YYText/YYTextLayout.h>
|
||||
#import <YYText/YYTextAttribute.h>
|
||||
#else
|
||||
#import "YYTextParser.h"
|
||||
#import "YYTextLayout.h"
|
||||
#import "YYTextAttribute.h"
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#if !TARGET_INTERFACE_BUILDER
|
||||
|
||||
/**
|
||||
The YYLabel class implements a read-only text view.
|
||||
|
||||
@discussion The API and behavior is similar to UILabel, but provides more features:
|
||||
|
||||
* It supports asynchronous layout and rendering (to avoid blocking UI thread).
|
||||
* It extends the CoreText attributes to support more text effects.
|
||||
* It allows to add UIImage, UIView and CALayer as text attachments.
|
||||
* It allows to add 'highlight' link to some range of text to allow user interact with.
|
||||
* It allows to add container path and exclusion paths to control text container's shape.
|
||||
* It supports vertical form layout to display CJK text.
|
||||
|
||||
See NSAttributedString+YYText.h for more convenience methods to set the attributes.
|
||||
See YYTextAttribute.h and YYTextLayout.h for more information.
|
||||
*/
|
||||
@interface YYLabel : UIView <NSCoding>
|
||||
|
||||
#pragma mark - Accessing the Text Attributes
|
||||
///=============================================================================
|
||||
/// @name Accessing the Text Attributes
|
||||
///=============================================================================
|
||||
|
||||
/**
|
||||
The text displayed by the label. Default is nil.
|
||||
Set a new value to this property also replaces the text in `attributedText`.
|
||||
Get the value returns the plain text in `attributedText`.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) NSString *text;
|
||||
|
||||
/**
|
||||
The font of the text. Default is 17-point system font.
|
||||
Set a new value to this property also causes the new font to be applied to the entire `attributedText`.
|
||||
Get the value returns the font at the head of `attributedText`.
|
||||
*/
|
||||
@property (null_resettable, nonatomic, strong) UIFont *font;
|
||||
|
||||
/**
|
||||
The color of the text. Default is black.
|
||||
Set a new value to this property also causes the new color to be applied to the entire `attributedText`.
|
||||
Get the value returns the color at the head of `attributedText`.
|
||||
*/
|
||||
@property (null_resettable, nonatomic, strong) UIColor *textColor;
|
||||
|
||||
/**
|
||||
The shadow color of the text. Default is nil.
|
||||
Set a new value to this property also causes the shadow color to be applied to the entire `attributedText`.
|
||||
Get the value returns the shadow color at the head of `attributedText`.
|
||||
*/
|
||||
@property (nullable, nonatomic, strong) UIColor *shadowColor;
|
||||
|
||||
/**
|
||||
The shadow offset of the text. Default is CGSizeZero.
|
||||
Set a new value to this property also causes the shadow offset to be applied to the entire `attributedText`.
|
||||
Get the value returns the shadow offset at the head of `attributedText`.
|
||||
*/
|
||||
@property (nonatomic) CGSize shadowOffset;
|
||||
|
||||
/**
|
||||
The shadow blur of the text. Default is 0.
|
||||
Set a new value to this property also causes the shadow blur to be applied to the entire `attributedText`.
|
||||
Get the value returns the shadow blur at the head of `attributedText`.
|
||||
*/
|
||||
@property (nonatomic) CGFloat shadowBlurRadius;
|
||||
|
||||
/**
|
||||
The technique to use for aligning the text. Default is NSTextAlignmentNatural.
|
||||
Set a new value to this property also causes the new alignment to be applied to the entire `attributedText`.
|
||||
Get the value returns the alignment at the head of `attributedText`.
|
||||
*/
|
||||
@property (nonatomic) NSTextAlignment textAlignment;
|
||||
|
||||
/**
|
||||
The text vertical aligmnent in container. Default is YYTextVerticalAlignmentCenter.
|
||||
*/
|
||||
@property (nonatomic) YYTextVerticalAlignment textVerticalAlignment;
|
||||
|
||||
/**
|
||||
The styled text displayed by the label.
|
||||
Set a new value to this property also replaces the value of the `text`, `font`, `textColor`,
|
||||
`textAlignment` and other properties in label.
|
||||
|
||||
@discussion It only support the attributes declared in CoreText and YYTextAttribute.
|
||||
See `NSAttributedString+YYText` for more convenience methods to set the attributes.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) NSAttributedString *attributedText;
|
||||
|
||||
/**
|
||||
The technique to use for wrapping and truncating the label's text.
|
||||
Default is NSLineBreakByTruncatingTail.
|
||||
*/
|
||||
@property (nonatomic) NSLineBreakMode lineBreakMode;
|
||||
|
||||
/**
|
||||
The truncation token string used when text is truncated. Default is nil.
|
||||
When the value is nil, the label use "…" as default truncation token.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) NSAttributedString *truncationToken;
|
||||
|
||||
/**
|
||||
The maximum number of lines to use for rendering text. Default value is 1.
|
||||
0 means no limit.
|
||||
*/
|
||||
@property (nonatomic) NSUInteger numberOfLines;
|
||||
|
||||
/**
|
||||
When `text` or `attributedText` is changed, the parser will be called to modify the text.
|
||||
It can be used to add code highlighting or emoticon replacement to text view.
|
||||
The default value is nil.
|
||||
|
||||
See `YYTextParser` protocol for more information.
|
||||
*/
|
||||
@property (nullable, nonatomic, strong) id<YYTextParser> textParser;
|
||||
|
||||
/**
|
||||
The current text layout in text view. It can be used to query the text layout information.
|
||||
Set a new value to this property also replaces most properties in this label, such as `text`,
|
||||
`color`, `attributedText`, `lineBreakMode`, `textContainerPath`, `exclusionPaths` and so on.
|
||||
*/
|
||||
@property (nullable, nonatomic, strong) YYTextLayout *textLayout;
|
||||
|
||||
|
||||
#pragma mark - Configuring the Text Container
|
||||
///=============================================================================
|
||||
/// @name Configuring the Text Container
|
||||
///=============================================================================
|
||||
|
||||
/**
|
||||
A UIBezierPath object that specifies the shape of the text frame. Default value is nil.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) UIBezierPath *textContainerPath;
|
||||
|
||||
/**
|
||||
An array of UIBezierPath objects representing the exclusion paths inside the
|
||||
receiver's bounding rectangle. Default value is nil.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) NSArray<UIBezierPath *> *exclusionPaths;
|
||||
|
||||
/**
|
||||
The inset of the text container's layout area within the text view's content area.
|
||||
Default value is UIEdgeInsetsZero.
|
||||
*/
|
||||
@property (nonatomic) UIEdgeInsets textContainerInset;
|
||||
|
||||
/**
|
||||
Whether the receiver's layout orientation is vertical form. Default is NO.
|
||||
It may used to display CJK text.
|
||||
*/
|
||||
@property (nonatomic, getter=isVerticalForm) BOOL verticalForm;
|
||||
|
||||
/**
|
||||
The text line position modifier used to modify the lines' position in layout.
|
||||
Default value is nil.
|
||||
See `YYTextLinePositionModifier` protocol for more information.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) id<YYTextLinePositionModifier> linePositionModifier;
|
||||
|
||||
/**
|
||||
The debug option to display CoreText layout result.
|
||||
The default value is [YYTextDebugOption sharedDebugOption].
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) YYTextDebugOption *debugOption;
|
||||
|
||||
|
||||
#pragma mark - Getting the Layout Constraints
|
||||
///=============================================================================
|
||||
/// @name Getting the Layout Constraints
|
||||
///=============================================================================
|
||||
|
||||
/**
|
||||
The preferred maximum width (in points) for a multiline label.
|
||||
|
||||
@discussion This property affects the size of the label when layout constraints
|
||||
are applied to it. During layout, if the text extends beyond the width
|
||||
specified by this property, the additional text is flowed to one or more new
|
||||
lines, thereby increasing the height of the label. If the text is vertical
|
||||
form, this value will match to text height.
|
||||
*/
|
||||
@property (nonatomic) CGFloat preferredMaxLayoutWidth;
|
||||
|
||||
|
||||
#pragma mark - Interacting with Text Data
|
||||
///=============================================================================
|
||||
/// @name Interacting with Text Data
|
||||
///=============================================================================
|
||||
|
||||
/**
|
||||
When user tap the label, this action will be called (similar to tap gesture).
|
||||
The default value is nil.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) YYTextAction textTapAction;
|
||||
|
||||
/**
|
||||
When user long press the label, this action will be called (similar to long press gesture).
|
||||
The default value is nil.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) YYTextAction textLongPressAction;
|
||||
|
||||
/**
|
||||
When user tap the highlight range of text, this action will be called.
|
||||
The default value is nil.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) YYTextAction highlightTapAction;
|
||||
|
||||
/**
|
||||
When user long press the highlight range of text, this action will be called.
|
||||
The default value is nil.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) YYTextAction highlightLongPressAction;
|
||||
|
||||
|
||||
#pragma mark - Configuring the Display Mode
|
||||
///=============================================================================
|
||||
/// @name Configuring the Display Mode
|
||||
///=============================================================================
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether the layout and rendering codes are running
|
||||
asynchronously on background threads.
|
||||
|
||||
The default value is `NO`.
|
||||
*/
|
||||
@property (nonatomic) BOOL displaysAsynchronously;
|
||||
|
||||
/**
|
||||
If the value is YES, and the layer is rendered asynchronously, then it will
|
||||
set label.layer.contents to nil before display.
|
||||
|
||||
The default value is `YES`.
|
||||
|
||||
@discussion When the asynchronously display is enabled, the layer's content will
|
||||
be updated after the background render process finished. If the render process
|
||||
can not finished in a vsync time (1/60 second), the old content will be still kept
|
||||
for display. You may manually clear the content by set the layer.contents to nil
|
||||
after you update the label's properties, or you can just set this property to YES.
|
||||
*/
|
||||
@property (nonatomic) BOOL clearContentsBeforeAsynchronouslyDisplay;
|
||||
|
||||
/**
|
||||
If the value is YES, and the layer is rendered asynchronously, then it will add
|
||||
a fade animation on layer when the contents of layer changed.
|
||||
|
||||
The default value is `YES`.
|
||||
*/
|
||||
@property (nonatomic) BOOL fadeOnAsynchronouslyDisplay;
|
||||
|
||||
/**
|
||||
If the value is YES, then it will add a fade animation on layer when some range
|
||||
of text become highlighted.
|
||||
|
||||
The default value is `YES`.
|
||||
*/
|
||||
@property (nonatomic) BOOL fadeOnHighlight;
|
||||
|
||||
/**
|
||||
Ignore common properties (such as text, font, textColor, attributedText...) and
|
||||
only use "textLayout" to display content.
|
||||
|
||||
The default value is `NO`.
|
||||
|
||||
@discussion If you control the label content only through "textLayout", then
|
||||
you may set this value to YES for higher performance.
|
||||
*/
|
||||
@property (nonatomic) BOOL ignoreCommonProperties;
|
||||
|
||||
/*
|
||||
Tips:
|
||||
|
||||
1. If you only need a UILabel alternative to display rich text and receive link touch event,
|
||||
you do not need to adjust the display mode properties.
|
||||
|
||||
2. If you have performance issues, you may enable the asynchronous display mode
|
||||
by setting the `displaysAsynchronously` to YES.
|
||||
|
||||
3. If you want to get the highest performance, you should do text layout with
|
||||
`YYTextLayout` class in background thread. Here's an example:
|
||||
|
||||
YYLabel *label = [YYLabel new];
|
||||
label.displaysAsynchronously = YES;
|
||||
label.ignoreCommonProperties = YES;
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
|
||||
// Create attributed string.
|
||||
NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:@"Some Text"];
|
||||
text.yy_font = [UIFont systemFontOfSize:16];
|
||||
text.yy_color = [UIColor grayColor];
|
||||
[text yy_setColor:[UIColor redColor] range:NSMakeRange(0, 4)];
|
||||
|
||||
// Create text container
|
||||
YYTextContainer *container = [YYTextContainer new];
|
||||
container.size = CGSizeMake(100, CGFLOAT_MAX);
|
||||
container.maximumNumberOfRows = 0;
|
||||
|
||||
// Generate a text layout.
|
||||
YYTextLayout *layout = [YYTextLayout layoutWithContainer:container text:text];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
label.size = layout.textBoundingSize;
|
||||
label.textLayout = layout;
|
||||
});
|
||||
});
|
||||
|
||||
*/
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#else // TARGET_INTERFACE_BUILDER
|
||||
IB_DESIGNABLE
|
||||
@interface YYLabel : UIView <NSCoding>
|
||||
@property (nullable, nonatomic, copy) IBInspectable NSString *text;
|
||||
@property (null_resettable, nonatomic, strong) IBInspectable UIColor *textColor;
|
||||
@property (nullable, nonatomic, strong) IBInspectable NSString *fontName_;
|
||||
@property (nonatomic) IBInspectable CGFloat fontSize_;
|
||||
@property (nonatomic) IBInspectable BOOL fontIsBold_;
|
||||
@property (nonatomic) IBInspectable NSUInteger numberOfLines;
|
||||
@property (nonatomic) IBInspectable NSInteger lineBreakMode;
|
||||
@property (nonatomic) IBInspectable CGFloat preferredMaxLayoutWidth;
|
||||
@property (nonatomic, getter=isVerticalForm) IBInspectable BOOL verticalForm;
|
||||
@property (nonatomic) IBInspectable NSInteger textAlignment;
|
||||
@property (nonatomic) IBInspectable NSInteger textVerticalAlignment;
|
||||
@property (nullable, nonatomic, strong) IBInspectable UIColor *shadowColor;
|
||||
@property (nonatomic) IBInspectable CGPoint shadowOffset;
|
||||
@property (nonatomic) IBInspectable CGFloat shadowBlurRadius;
|
||||
@property (nullable, nonatomic, copy) IBInspectable NSAttributedString *attributedText;
|
||||
@property (nonatomic) IBInspectable CGFloat insetTop_;
|
||||
@property (nonatomic) IBInspectable CGFloat insetBottom_;
|
||||
@property (nonatomic) IBInspectable CGFloat insetLeft_;
|
||||
@property (nonatomic) IBInspectable CGFloat insetRight_;
|
||||
@property (nonatomic) IBInspectable BOOL debugEnabled_;
|
||||
|
||||
@property (null_resettable, nonatomic, strong) UIFont *font;
|
||||
@property (nullable, nonatomic, copy) NSAttributedString *truncationToken;
|
||||
@property (nullable, nonatomic, strong) id<YYTextParser> textParser;
|
||||
@property (nullable, nonatomic, strong) YYTextLayout *textLayout;
|
||||
@property (nullable, nonatomic, copy) UIBezierPath *textContainerPath;
|
||||
@property (nullable, nonatomic, copy) NSArray<UIBezierPath*> *exclusionPaths;
|
||||
@property (nonatomic) UIEdgeInsets textContainerInset;
|
||||
@property (nullable, nonatomic, copy) id<YYTextLinePositionModifier> linePositionModifier;
|
||||
@property (nonnull, nonatomic, copy) YYTextDebugOption *debugOption;
|
||||
@property (nullable, nonatomic, copy) YYTextAction textTapAction;
|
||||
@property (nullable, nonatomic, copy) YYTextAction textLongPressAction;
|
||||
@property (nullable, nonatomic, copy) YYTextAction highlightTapAction;
|
||||
@property (nullable, nonatomic, copy) YYTextAction highlightLongPressAction;
|
||||
@property (nonatomic) BOOL displaysAsynchronously;
|
||||
@property (nonatomic) BOOL clearContentsBeforeAsynchronouslyDisplay;
|
||||
@property (nonatomic) BOOL fadeOnAsynchronouslyDisplay;
|
||||
@property (nonatomic) BOOL fadeOnHighlight;
|
||||
@property (nonatomic) BOOL ignoreCommonProperties;
|
||||
@end
|
||||
#endif // !TARGET_INTERFACE_BUILDER
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// YYText.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/2/25.
|
||||
// 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(<YYText/YYText.h>)
|
||||
FOUNDATION_EXPORT double YYTextVersionNumber;
|
||||
FOUNDATION_EXPORT const unsigned char YYTextVersionString[];
|
||||
#import <YYText/YYLabel.h>
|
||||
#import <YYText/YYTextView.h>
|
||||
#import <YYText/YYTextAttribute.h>
|
||||
#import <YYText/YYTextArchiver.h>
|
||||
#import <YYText/YYTextParser.h>
|
||||
#import <YYText/YYTextRunDelegate.h>
|
||||
#import <YYText/YYTextRubyAnnotation.h>
|
||||
#import <YYText/YYTextLayout.h>
|
||||
#import <YYText/YYTextLine.h>
|
||||
#import <YYText/YYTextInput.h>
|
||||
#import <YYText/YYTextDebugOption.h>
|
||||
#import <YYText/YYTextKeyboardManager.h>
|
||||
#import <YYText/YYTextUtilities.h>
|
||||
#import <YYText/NSAttributedString+YYText.h>
|
||||
#import <YYText/NSParagraphStyle+YYText.h>
|
||||
#import <YYText/UIPasteboard+YYText.h>
|
||||
#else
|
||||
#import "YYLabel.h"
|
||||
#import "YYTextView.h"
|
||||
#import "YYTextAttribute.h"
|
||||
#import "YYTextArchiver.h"
|
||||
#import "YYTextParser.h"
|
||||
#import "YYTextRunDelegate.h"
|
||||
#import "YYTextRubyAnnotation.h"
|
||||
#import "YYTextLayout.h"
|
||||
#import "YYTextLine.h"
|
||||
#import "YYTextInput.h"
|
||||
#import "YYTextDebugOption.h"
|
||||
#import "YYTextKeyboardManager.h"
|
||||
#import "YYTextUtilities.h"
|
||||
#import "NSAttributedString+YYText.h"
|
||||
#import "NSParagraphStyle+YYText.h"
|
||||
#import "UIPasteboard+YYText.h"
|
||||
#endif
|
|
@ -0,0 +1,410 @@
|
|||
//
|
||||
// YYTextView.h
|
||||
// YYText <https://github.com/ibireme/YYText>
|
||||
//
|
||||
// Created by ibireme on 15/2/25.
|
||||
// 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(<YYText/YYText.h>)
|
||||
#import <YYText/YYTextParser.h>
|
||||
#import <YYText/YYTextLayout.h>
|
||||
#import <YYText/YYTextAttribute.h>
|
||||
#else
|
||||
#import "YYTextParser.h"
|
||||
#import "YYTextLayout.h"
|
||||
#import "YYTextAttribute.h"
|
||||
#endif
|
||||
|
||||
@class YYTextView;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
The YYTextViewDelegate protocol defines a set of optional methods you can use
|
||||
to receive editing-related messages for YYTextView objects.
|
||||
|
||||
@discussion The API and behavior is similar to UITextViewDelegate,
|
||||
see UITextViewDelegate's documentation for more information.
|
||||
*/
|
||||
@protocol YYTextViewDelegate <NSObject, UIScrollViewDelegate>
|
||||
@optional
|
||||
- (BOOL)textViewShouldBeginEditing:(YYTextView *)textView;
|
||||
- (BOOL)textViewShouldEndEditing:(YYTextView *)textView;
|
||||
- (void)textViewDidBeginEditing:(YYTextView *)textView;
|
||||
- (void)textViewDidEndEditing:(YYTextView *)textView;
|
||||
- (BOOL)textView:(YYTextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text;
|
||||
- (void)textViewDidChange:(YYTextView *)textView;
|
||||
- (void)textViewDidChangeSelection:(YYTextView *)textView;
|
||||
|
||||
- (BOOL)textView:(YYTextView *)textView shouldTapHighlight:(YYTextHighlight *)highlight inRange:(NSRange)characterRange;
|
||||
- (void)textView:(YYTextView *)textView didTapHighlight:(YYTextHighlight *)highlight inRange:(NSRange)characterRange rect:(CGRect)rect;
|
||||
- (BOOL)textView:(YYTextView *)textView shouldLongPressHighlight:(YYTextHighlight *)highlight inRange:(NSRange)characterRange;
|
||||
- (void)textView:(YYTextView *)textView didLongPressHighlight:(YYTextHighlight *)highlight inRange:(NSRange)characterRange rect:(CGRect)rect;
|
||||
@end
|
||||
|
||||
|
||||
#if !TARGET_INTERFACE_BUILDER
|
||||
|
||||
/**
|
||||
The YYTextView class implements the behavior for a scrollable, multiline text region.
|
||||
|
||||
@discussion The API and behavior is similar to UITextView, but provides more features:
|
||||
|
||||
* It extends the CoreText attributes to support more text effects.
|
||||
* It allows to add UIImage, UIView and CALayer as text attachments.
|
||||
* It allows to add 'highlight' link to some range of text to allow user interact with.
|
||||
* It allows to add exclusion paths to control text container's shape.
|
||||
* It supports vertical form layout to display and edit CJK text.
|
||||
* It allows user to copy/paste image and attributed text from/to text view.
|
||||
* It allows to set an attributed text as placeholder.
|
||||
|
||||
See NSAttributedString+YYText.h for more convenience methods to set the attributes.
|
||||
See YYTextAttribute.h and YYTextLayout.h for more information.
|
||||
*/
|
||||
@interface YYTextView : UIScrollView <UITextInput>
|
||||
|
||||
|
||||
#pragma mark - Accessing the Delegate
|
||||
///=============================================================================
|
||||
/// @name Accessing the Delegate
|
||||
///=============================================================================
|
||||
|
||||
@property (nullable, nonatomic, weak) id<YYTextViewDelegate> delegate;
|
||||
|
||||
|
||||
#pragma mark - Configuring the Text Attributes
|
||||
///=============================================================================
|
||||
/// @name Configuring the Text Attributes
|
||||
///=============================================================================
|
||||
|
||||
/**
|
||||
The text displayed by the text view.
|
||||
Set a new value to this property also replaces the text in `attributedText`.
|
||||
Get the value returns the plain text in `attributedText`.
|
||||
*/
|
||||
@property (null_resettable, nonatomic, copy) NSString *text;
|
||||
|
||||
/**
|
||||
The font of the text. Default is 12-point system font.
|
||||
Set a new value to this property also causes the new font to be applied to the entire `attributedText`.
|
||||
Get the value returns the font at the head of `attributedText`.
|
||||
*/
|
||||
@property (nullable, nonatomic, strong) UIFont *font;
|
||||
|
||||
/**
|
||||
The color of the text. Default is black.
|
||||
Set a new value to this property also causes the new color to be applied to the entire `attributedText`.
|
||||
Get the value returns the color at the head of `attributedText`.
|
||||
*/
|
||||
@property (nullable, nonatomic, strong) UIColor *textColor;
|
||||
|
||||
/**
|
||||
The technique to use for aligning the text. Default is NSTextAlignmentNatural.
|
||||
Set a new value to this property also causes the new alignment to be applied to the entire `attributedText`.
|
||||
Get the value returns the alignment at the head of `attributedText`.
|
||||
*/
|
||||
@property (nonatomic) NSTextAlignment textAlignment;
|
||||
|
||||
/**
|
||||
The text vertical aligmnent in container. Default is YYTextVerticalAlignmentTop.
|
||||
*/
|
||||
@property (nonatomic) YYTextVerticalAlignment textVerticalAlignment;
|
||||
|
||||
/**
|
||||
The types of data converted to clickable URLs in the text view. Default is UIDataDetectorTypeNone.
|
||||
The tap or long press action should be handled by delegate.
|
||||
*/
|
||||
@property (nonatomic) UIDataDetectorTypes dataDetectorTypes;
|
||||
|
||||
/**
|
||||
The attributes to apply to links at normal state. Default is light blue color.
|
||||
When a range of text is detected by the `dataDetectorTypes`, this value would be
|
||||
used to modify the original attributes in the range.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) NSDictionary<NSString *, id> *linkTextAttributes;
|
||||
|
||||
/**
|
||||
The attributes to apply to links at highlight state. Default is a gray border.
|
||||
When a range of text is detected by the `dataDetectorTypes` and the range was touched by user,
|
||||
this value would be used to modify the original attributes in the range.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) NSDictionary<NSString *, id> *highlightTextAttributes;
|
||||
|
||||
/**
|
||||
The attributes to apply to new text being entered by the user.
|
||||
When the text view's selection changes, this value is reset automatically.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) NSDictionary<NSString *, id> *typingAttributes;
|
||||
|
||||
/**
|
||||
The styled text displayed by the text view.
|
||||
Set a new value to this property also replaces the value of the `text`, `font`, `textColor`,
|
||||
`textAlignment` and other properties in text view.
|
||||
|
||||
@discussion It only support the attributes declared in CoreText and YYTextAttribute.
|
||||
See `NSAttributedString+YYText` for more convenience methods to set the attributes.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) NSAttributedString *attributedText;
|
||||
|
||||
/**
|
||||
When `text` or `attributedText` is changed, the parser will be called to modify the text.
|
||||
It can be used to add code highlighting or emoticon replacement to text view.
|
||||
The default value is nil.
|
||||
|
||||
See `YYTextParser` protocol for more information.
|
||||
*/
|
||||
@property (nullable, nonatomic, strong) id<YYTextParser> textParser;
|
||||
|
||||
/**
|
||||
The current text layout in text view (readonly).
|
||||
It can be used to query the text layout information.
|
||||
*/
|
||||
@property (nullable, nonatomic, strong, readonly) YYTextLayout *textLayout;
|
||||
|
||||
|
||||
#pragma mark - Configuring the Placeholder
|
||||
///=============================================================================
|
||||
/// @name Configuring the Placeholder
|
||||
///=============================================================================
|
||||
|
||||
/**
|
||||
The placeholder text displayed by the text view (when the text view is empty).
|
||||
Set a new value to this property also replaces the text in `placeholderAttributedText`.
|
||||
Get the value returns the plain text in `placeholderAttributedText`.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) NSString *placeholderText;
|
||||
|
||||
/**
|
||||
The font of the placeholder text. Default is same as `font` property.
|
||||
Set a new value to this property also causes the new font to be applied to the entire `placeholderAttributedText`.
|
||||
Get the value returns the font at the head of `placeholderAttributedText`.
|
||||
*/
|
||||
@property (nullable, nonatomic, strong) UIFont *placeholderFont;
|
||||
|
||||
/**
|
||||
The color of the placeholder text. Default is gray.
|
||||
Set a new value to this property also causes the new color to be applied to the entire `placeholderAttributedText`.
|
||||
Get the value returns the color at the head of `placeholderAttributedText`.
|
||||
*/
|
||||
@property (nullable, nonatomic, strong) UIColor *placeholderTextColor;
|
||||
|
||||
/**
|
||||
The styled placeholder text displayed by the text view (when the text view is empty).
|
||||
Set a new value to this property also replaces the value of the `placeholderText`,
|
||||
`placeholderFont`, `placeholderTextColor`.
|
||||
|
||||
@discussion It only support the attributes declared in CoreText and YYTextAttribute.
|
||||
See `NSAttributedString+YYText` for more convenience methods to set the attributes.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) NSAttributedString *placeholderAttributedText;
|
||||
|
||||
|
||||
#pragma mark - Configuring the Text Container
|
||||
///=============================================================================
|
||||
/// @name Configuring the Text Container
|
||||
///=============================================================================
|
||||
|
||||
/**
|
||||
The inset of the text container's layout area within the text view's content area.
|
||||
*/
|
||||
@property (nonatomic) UIEdgeInsets textContainerInset;
|
||||
|
||||
/**
|
||||
An array of UIBezierPath objects representing the exclusion paths inside the
|
||||
receiver's bounding rectangle. Default value is nil.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) NSArray<UIBezierPath *> *exclusionPaths;
|
||||
|
||||
/**
|
||||
Whether the receiver's layout orientation is vertical form. Default is NO.
|
||||
It may used to edit/display CJK text.
|
||||
*/
|
||||
@property (nonatomic, getter=isVerticalForm) BOOL verticalForm;
|
||||
|
||||
/**
|
||||
The text line position modifier used to modify the lines' position in layout.
|
||||
See `YYTextLinePositionModifier` protocol for more information.
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) id<YYTextLinePositionModifier> linePositionModifier;
|
||||
|
||||
/**
|
||||
The debug option to display CoreText layout result.
|
||||
The default value is [YYTextDebugOption sharedDebugOption].
|
||||
*/
|
||||
@property (nullable, nonatomic, copy) YYTextDebugOption *debugOption;
|
||||
|
||||
|
||||
#pragma mark - Working with the Selection and Menu
|
||||
///=============================================================================
|
||||
/// @name Working with the Selection and Menu
|
||||
///=============================================================================
|
||||
|
||||
/**
|
||||
Scrolls the receiver until the text in the specified range is visible.
|
||||
*/
|
||||
- (void)scrollRangeToVisible:(NSRange)range;
|
||||
|
||||
/**
|
||||
The current selection range of the receiver.
|
||||
*/
|
||||
@property (nonatomic) NSRange selectedRange;
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether inserting text replaces the previous contents.
|
||||
The default value is NO.
|
||||
*/
|
||||
@property (nonatomic) BOOL clearsOnInsertion;
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether the receiver is selectable. Default is YES.
|
||||
When the value of this property is NO, user cannot select content or edit text.
|
||||
*/
|
||||
@property (nonatomic, getter=isSelectable) BOOL selectable;
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether the receiver is highlightable. Default is YES.
|
||||
When the value of this property is NO, user cannot interact with the highlight range of text.
|
||||
*/
|
||||
@property (nonatomic, getter=isHighlightable) BOOL highlightable;
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether the receiver is editable. Default is YES.
|
||||
When the value of this property is NO, user cannot edit text.
|
||||
*/
|
||||
@property (nonatomic, getter=isEditable) BOOL editable;
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether the receiver can paste image from pasteboard. Default is NO.
|
||||
When the value of this property is YES, user can paste image from pasteboard via "paste" menu.
|
||||
*/
|
||||
@property (nonatomic) BOOL allowsPasteImage;
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether the receiver can paste attributed text from pasteboard. Default is NO.
|
||||
When the value of this property is YES, user can paste attributed text from pasteboard via "paste" menu.
|
||||
*/
|
||||
@property (nonatomic) BOOL allowsPasteAttributedString;
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether the receiver can copy attributed text to pasteboard. Default is YES.
|
||||
When the value of this property is YES, user can copy attributed text (with attachment image)
|
||||
from text view to pasteboard via "copy" menu.
|
||||
*/
|
||||
@property (nonatomic) BOOL allowsCopyAttributedString;
|
||||
|
||||
|
||||
#pragma mark - Manage the undo and redo
|
||||
///=============================================================================
|
||||
/// @name Manage the undo and redo
|
||||
///=============================================================================
|
||||
|
||||
/**
|
||||
A Boolean value indicating whether the receiver can undo and redo typing with
|
||||
shake gesture. The default value is YES.
|
||||
*/
|
||||
@property (nonatomic) BOOL allowsUndoAndRedo;
|
||||
|
||||
/**
|
||||
The maximum undo/redo level. The default value is 20.
|
||||
*/
|
||||
@property (nonatomic) NSUInteger maximumUndoLevel;
|
||||
|
||||
|
||||
#pragma mark - Replacing the System Input Views
|
||||
///=============================================================================
|
||||
/// @name Replacing the System Input Views
|
||||
///=============================================================================
|
||||
|
||||
/**
|
||||
The custom input view to display when the text view becomes the first responder.
|
||||
It can be used to replace system keyboard.
|
||||
|
||||
@discussion If set the value while first responder, it will not take effect until
|
||||
'reloadInputViews' is called.
|
||||
*/
|
||||
@property (nullable, nonatomic, readwrite, strong) __kindof UIView *inputView;
|
||||
|
||||
/**
|
||||
The custom accessory view to display when the text view becomes the first responder.
|
||||
It can be used to add a toolbar at the top of keyboard.
|
||||
|
||||
@discussion If set the value while first responder, it will not take effect until
|
||||
'reloadInputViews' is called.
|
||||
*/
|
||||
@property (nullable, nonatomic, readwrite, strong) __kindof UIView *inputAccessoryView;
|
||||
|
||||
/**
|
||||
If you use an custom accessory view without "inputAccessoryView" property,
|
||||
you may set the accessory view's height. It may used by auto scroll calculation.
|
||||
*/
|
||||
@property (nonatomic) CGFloat extraAccessoryViewHeight;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#else // TARGET_INTERFACE_BUILDER
|
||||
IB_DESIGNABLE
|
||||
@interface YYTextView : UIScrollView <UITextInput>
|
||||
@property (null_resettable, nonatomic, copy) IBInspectable NSString *text;
|
||||
@property (nullable, nonatomic, strong) IBInspectable UIColor *textColor;
|
||||
@property (nullable, nonatomic, strong) IBInspectable NSString *fontName_;
|
||||
@property (nonatomic) IBInspectable CGFloat fontSize_;
|
||||
@property (nonatomic) IBInspectable BOOL fontIsBold_;
|
||||
@property (nonatomic) IBInspectable NSTextAlignment textAlignment;
|
||||
@property (nonatomic) IBInspectable YYTextVerticalAlignment textVerticalAlignment;
|
||||
@property (nullable, nonatomic, copy) IBInspectable NSString *placeholderText;
|
||||
@property (nullable, nonatomic, strong) IBInspectable UIColor *placeholderTextColor;
|
||||
@property (nullable, nonatomic, strong) IBInspectable NSString *placeholderFontName_;
|
||||
@property (nonatomic) IBInspectable CGFloat placeholderFontSize_;
|
||||
@property (nonatomic) IBInspectable BOOL placeholderFontIsBold_;
|
||||
@property (nonatomic, getter=isVerticalForm) IBInspectable BOOL verticalForm;
|
||||
@property (nonatomic) IBInspectable BOOL clearsOnInsertion;
|
||||
@property (nonatomic, getter=isSelectable) IBInspectable BOOL selectable;
|
||||
@property (nonatomic, getter=isHighlightable) IBInspectable BOOL highlightable;
|
||||
@property (nonatomic, getter=isEditable) IBInspectable BOOL editable;
|
||||
@property (nonatomic) IBInspectable BOOL allowsPasteImage;
|
||||
@property (nonatomic) IBInspectable BOOL allowsPasteAttributedString;
|
||||
@property (nonatomic) IBInspectable BOOL allowsCopyAttributedString;
|
||||
@property (nonatomic) IBInspectable BOOL allowsUndoAndRedo;
|
||||
@property (nonatomic) IBInspectable NSUInteger maximumUndoLevel;
|
||||
@property (nonatomic) IBInspectable CGFloat insetTop_;
|
||||
@property (nonatomic) IBInspectable CGFloat insetBottom_;
|
||||
@property (nonatomic) IBInspectable CGFloat insetLeft_;
|
||||
@property (nonatomic) IBInspectable CGFloat insetRight_;
|
||||
@property (nonatomic) IBInspectable BOOL debugEnabled_;
|
||||
@property (nullable, nonatomic, weak) id<YYTextViewDelegate> delegate;
|
||||
@property (nullable, nonatomic, strong) UIFont *font;
|
||||
@property (nonatomic) UIDataDetectorTypes dataDetectorTypes;
|
||||
@property (nullable, nonatomic, copy) NSDictionary *linkTextAttributes;
|
||||
@property (nullable, nonatomic, copy) NSDictionary *highlightTextAttributes;
|
||||
@property (nullable, nonatomic, copy) NSDictionary *typingAttributes;
|
||||
@property (nullable, nonatomic, copy) NSAttributedString *attributedText;
|
||||
@property (nullable, nonatomic, strong) id<YYTextParser> textParser;
|
||||
@property (nullable, nonatomic, strong, readonly) YYTextLayout *textLayout;
|
||||
@property (nullable, nonatomic, strong) UIFont *placeholderFont;
|
||||
@property (nullable, nonatomic, copy) NSAttributedString *placeholderAttributedText;
|
||||
@property (nonatomic) UIEdgeInsets textContainerInset;
|
||||
@property (nullable, nonatomic, copy) NSArray *exclusionPaths;
|
||||
@property (nullable, nonatomic, copy) id<YYTextLinePositionModifier> linePositionModifier;
|
||||
@property (nullable, nonatomic, copy) YYTextDebugOption *debugOption;
|
||||
- (void)scrollRangeToVisible:(NSRange)range;
|
||||
@property (nonatomic) NSRange selectedRange;
|
||||
@property (nullable, nonatomic, readwrite, strong) __kindof UIView *inputView;
|
||||
@property (nullable, nonatomic, readwrite, strong) __kindof UIView *inputAccessoryView;
|
||||
@property (nonatomic) CGFloat extraAccessoryViewHeight;
|
||||
@end
|
||||
#endif // !TARGET_INTERFACE_BUILDER
|
||||
|
||||
|
||||
// Notifications, see UITextView's documentation for more information.
|
||||
UIKIT_EXTERN NSString *const YYTextViewTextDidBeginEditingNotification;
|
||||
UIKIT_EXTERN NSString *const YYTextViewTextDidChangeNotification;
|
||||
UIKIT_EXTERN NSString *const YYTextViewTextDidEndEditingNotification;
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
File diff suppressed because it is too large
Load Diff
|
@ -8,6 +8,20 @@
|
|||
|
||||
/* Begin PBXBuildFile section */
|
||||
664551746CA42328436EB43A /* Pods_ProductApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7E9D1598912BF8D031CA536 /* Pods_ProductApp.framework */; };
|
||||
CA8181882C9E58B100EE7E6E /* StartKTXZWenZhangViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CA8181872C9E58B100EE7E6E /* StartKTXZWenZhangViewController.m */; };
|
||||
CA81818B2C9E598300EE7E6E /* StartKTXZWenZhangView.m in Sources */ = {isa = PBXBuildFile; fileRef = CA81818A2C9E598300EE7E6E /* StartKTXZWenZhangView.m */; };
|
||||
CA81818E2C9E60B100EE7E6E /* ViewLable.m in Sources */ = {isa = PBXBuildFile; fileRef = CA81818D2C9E60B100EE7E6E /* ViewLable.m */; };
|
||||
CA8181922C9E748500EE7E6E /* StartKTXZPGViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CA8181912C9E748500EE7E6E /* StartKTXZPGViewController.m */; };
|
||||
CA8181962C9E751700EE7E6E /* StartKTXZPGView.m in Sources */ = {isa = PBXBuildFile; fileRef = CA8181952C9E751700EE7E6E /* StartKTXZPGView.m */; };
|
||||
CA8181992C9E77FB00EE7E6E /* DownJDLoadView.m in Sources */ = {isa = PBXBuildFile; fileRef = CA8181972C9E77FB00EE7E6E /* DownJDLoadView.m */; };
|
||||
CA81819D2C9E9C6600EE7E6E /* StartKTXZPGDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CA81819C2C9E9C6600EE7E6E /* StartKTXZPGDetailViewController.m */; };
|
||||
CA8181A12C9E9CC500EE7E6E /* StartKTXZPGDetailView.m in Sources */ = {isa = PBXBuildFile; fileRef = CA8181A02C9E9CC500EE7E6E /* StartKTXZPGDetailView.m */; };
|
||||
CA8181A42C9EA07300EE7E6E /* StartKTXZPGDetailTabView.m in Sources */ = {isa = PBXBuildFile; fileRef = CA8181A32C9EA07300EE7E6E /* StartKTXZPGDetailTabView.m */; };
|
||||
CA8181A72C9EAB8700EE7E6E /* PGJUILabel.m in Sources */ = {isa = PBXBuildFile; fileRef = CA8181A62C9EAB8700EE7E6E /* PGJUILabel.m */; };
|
||||
CA8181AD2C9EB95700EE7E6E /* StartKTXZPGDetailZHView.m in Sources */ = {isa = PBXBuildFile; fileRef = CA8181AC2C9EB95700EE7E6E /* StartKTXZPGDetailZHView.m */; };
|
||||
CA8181B02C9EB96000EE7E6E /* StartKTXZPGDetailFJView.m in Sources */ = {isa = PBXBuildFile; fileRef = CA8181AF2C9EB96000EE7E6E /* StartKTXZPGDetailFJView.m */; };
|
||||
CA8181B32C9EB96F00EE7E6E /* StartKTXZPGDetailXZView.m in Sources */ = {isa = PBXBuildFile; fileRef = CA8181B22C9EB96F00EE7E6E /* StartKTXZPGDetailXZView.m */; };
|
||||
CA8181B62C9ECB5800EE7E6E /* StartKTXZPGDetailFJItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = CA8181B52C9ECB5800EE7E6E /* StartKTXZPGDetailFJItemView.m */; };
|
||||
CABA14612C9D0B1C002CB3B1 /* ShouYeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CABA14602C9D0B1C002CB3B1 /* ShouYeViewController.m */; };
|
||||
CABA14642C9D0B25002CB3B1 /* FanWenViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CABA14632C9D0B25002CB3B1 /* FanWenViewController.m */; };
|
||||
CABA14672C9D0B31002CB3B1 /* AIChatViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CABA14662C9D0B31002CB3B1 /* AIChatViewController.m */; };
|
||||
|
@ -213,6 +227,34 @@
|
|||
/* Begin PBXFileReference section */
|
||||
87167CC240BDAA55C86F80DE /* Pods-ProductApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ProductApp.debug.xcconfig"; path = "Target Support Files/Pods-ProductApp/Pods-ProductApp.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
B3DBEC0C6ED0E182F65EDBEA /* Pods-ProductApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ProductApp.release.xcconfig"; path = "Target Support Files/Pods-ProductApp/Pods-ProductApp.release.xcconfig"; sourceTree = "<group>"; };
|
||||
CA8181862C9E58B100EE7E6E /* StartKTXZWenZhangViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StartKTXZWenZhangViewController.h; sourceTree = "<group>"; };
|
||||
CA8181872C9E58B100EE7E6E /* StartKTXZWenZhangViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StartKTXZWenZhangViewController.m; sourceTree = "<group>"; };
|
||||
CA8181892C9E598300EE7E6E /* StartKTXZWenZhangView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StartKTXZWenZhangView.h; sourceTree = "<group>"; };
|
||||
CA81818A2C9E598300EE7E6E /* StartKTXZWenZhangView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StartKTXZWenZhangView.m; sourceTree = "<group>"; };
|
||||
CA81818C2C9E60B100EE7E6E /* ViewLable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewLable.h; sourceTree = "<group>"; };
|
||||
CA81818D2C9E60B100EE7E6E /* ViewLable.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewLable.m; sourceTree = "<group>"; };
|
||||
CA8181902C9E748500EE7E6E /* StartKTXZPGViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StartKTXZPGViewController.h; sourceTree = "<group>"; };
|
||||
CA8181912C9E748500EE7E6E /* StartKTXZPGViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StartKTXZPGViewController.m; sourceTree = "<group>"; };
|
||||
CA8181942C9E751700EE7E6E /* StartKTXZPGView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StartKTXZPGView.h; sourceTree = "<group>"; };
|
||||
CA8181952C9E751700EE7E6E /* StartKTXZPGView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StartKTXZPGView.m; sourceTree = "<group>"; };
|
||||
CA8181972C9E77FB00EE7E6E /* DownJDLoadView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DownJDLoadView.m; sourceTree = "<group>"; };
|
||||
CA8181982C9E77FB00EE7E6E /* DownJDLoadView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DownJDLoadView.h; sourceTree = "<group>"; };
|
||||
CA81819B2C9E9C6600EE7E6E /* StartKTXZPGDetailViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StartKTXZPGDetailViewController.h; sourceTree = "<group>"; };
|
||||
CA81819C2C9E9C6600EE7E6E /* StartKTXZPGDetailViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StartKTXZPGDetailViewController.m; sourceTree = "<group>"; };
|
||||
CA81819F2C9E9CC500EE7E6E /* StartKTXZPGDetailView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StartKTXZPGDetailView.h; sourceTree = "<group>"; };
|
||||
CA8181A02C9E9CC500EE7E6E /* StartKTXZPGDetailView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StartKTXZPGDetailView.m; sourceTree = "<group>"; };
|
||||
CA8181A22C9EA07300EE7E6E /* StartKTXZPGDetailTabView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StartKTXZPGDetailTabView.h; sourceTree = "<group>"; };
|
||||
CA8181A32C9EA07300EE7E6E /* StartKTXZPGDetailTabView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StartKTXZPGDetailTabView.m; sourceTree = "<group>"; };
|
||||
CA8181A52C9EAB8700EE7E6E /* PGJUILabel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PGJUILabel.h; sourceTree = "<group>"; };
|
||||
CA8181A62C9EAB8700EE7E6E /* PGJUILabel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PGJUILabel.m; sourceTree = "<group>"; };
|
||||
CA8181AB2C9EB95700EE7E6E /* StartKTXZPGDetailZHView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StartKTXZPGDetailZHView.h; sourceTree = "<group>"; };
|
||||
CA8181AC2C9EB95700EE7E6E /* StartKTXZPGDetailZHView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StartKTXZPGDetailZHView.m; sourceTree = "<group>"; };
|
||||
CA8181AE2C9EB96000EE7E6E /* StartKTXZPGDetailFJView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StartKTXZPGDetailFJView.h; sourceTree = "<group>"; };
|
||||
CA8181AF2C9EB96000EE7E6E /* StartKTXZPGDetailFJView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StartKTXZPGDetailFJView.m; sourceTree = "<group>"; };
|
||||
CA8181B12C9EB96F00EE7E6E /* StartKTXZPGDetailXZView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StartKTXZPGDetailXZView.h; sourceTree = "<group>"; };
|
||||
CA8181B22C9EB96F00EE7E6E /* StartKTXZPGDetailXZView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StartKTXZPGDetailXZView.m; sourceTree = "<group>"; };
|
||||
CA8181B42C9ECB5800EE7E6E /* StartKTXZPGDetailFJItemView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StartKTXZPGDetailFJItemView.h; sourceTree = "<group>"; };
|
||||
CA8181B52C9ECB5800EE7E6E /* StartKTXZPGDetailFJItemView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StartKTXZPGDetailFJItemView.m; sourceTree = "<group>"; };
|
||||
CABA145F2C9D0B1C002CB3B1 /* ShouYeViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ShouYeViewController.h; sourceTree = "<group>"; };
|
||||
CABA14602C9D0B1C002CB3B1 /* ShouYeViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ShouYeViewController.m; sourceTree = "<group>"; };
|
||||
CABA14622C9D0B25002CB3B1 /* FanWenViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FanWenViewController.h; sourceTree = "<group>"; };
|
||||
|
@ -661,6 +703,80 @@
|
|||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CA81818F2C9E742400EE7E6E /* 批改识别 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CA8181932C9E750900EE7E6E /* view */,
|
||||
CA8181902C9E748500EE7E6E /* StartKTXZPGViewController.h */,
|
||||
CA8181912C9E748500EE7E6E /* StartKTXZPGViewController.m */,
|
||||
);
|
||||
path = "批改识别";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CA8181932C9E750900EE7E6E /* view */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CA8181942C9E751700EE7E6E /* StartKTXZPGView.h */,
|
||||
CA8181952C9E751700EE7E6E /* StartKTXZPGView.m */,
|
||||
CA8181982C9E77FB00EE7E6E /* DownJDLoadView.h */,
|
||||
CA8181972C9E77FB00EE7E6E /* DownJDLoadView.m */,
|
||||
);
|
||||
path = view;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CA81819A2C9E9C3100EE7E6E /* 批改信息 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CA81819E2C9E9CB900EE7E6E /* view */,
|
||||
CA81819B2C9E9C6600EE7E6E /* StartKTXZPGDetailViewController.h */,
|
||||
CA81819C2C9E9C6600EE7E6E /* StartKTXZPGDetailViewController.m */,
|
||||
);
|
||||
path = "批改信息";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CA81819E2C9E9CB900EE7E6E /* view */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CA8181AA2C9EB92C00EE7E6E /* 写作思路 */,
|
||||
CA8181A92C9EB92800EE7E6E /* 分句点评 */,
|
||||
CA8181A82C9EB91800EE7E6E /* 综合评价 */,
|
||||
CA81819F2C9E9CC500EE7E6E /* StartKTXZPGDetailView.h */,
|
||||
CA8181A02C9E9CC500EE7E6E /* StartKTXZPGDetailView.m */,
|
||||
CA8181A22C9EA07300EE7E6E /* StartKTXZPGDetailTabView.h */,
|
||||
CA8181A32C9EA07300EE7E6E /* StartKTXZPGDetailTabView.m */,
|
||||
);
|
||||
path = view;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CA8181A82C9EB91800EE7E6E /* 综合评价 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CA8181AB2C9EB95700EE7E6E /* StartKTXZPGDetailZHView.h */,
|
||||
CA8181AC2C9EB95700EE7E6E /* StartKTXZPGDetailZHView.m */,
|
||||
);
|
||||
path = "综合评价";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CA8181A92C9EB92800EE7E6E /* 分句点评 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CA8181AE2C9EB96000EE7E6E /* StartKTXZPGDetailFJView.h */,
|
||||
CA8181AF2C9EB96000EE7E6E /* StartKTXZPGDetailFJView.m */,
|
||||
CA8181B42C9ECB5800EE7E6E /* StartKTXZPGDetailFJItemView.h */,
|
||||
CA8181B52C9ECB5800EE7E6E /* StartKTXZPGDetailFJItemView.m */,
|
||||
);
|
||||
path = "分句点评";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CA8181AA2C9EB92C00EE7E6E /* 写作思路 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CA8181B12C9EB96F00EE7E6E /* StartKTXZPGDetailXZView.h */,
|
||||
CA8181B22C9EB96F00EE7E6E /* StartKTXZPGDetailXZView.m */,
|
||||
);
|
||||
path = "写作思路";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CABA145A2C9D0AF2002CB3B1 /* 首页 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -853,9 +969,13 @@
|
|||
CABA14E42C9D76B7002CB3B1 /* 课堂写作 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CA81819A2C9E9C3100EE7E6E /* 批改信息 */,
|
||||
CA81818F2C9E742400EE7E6E /* 批改识别 */,
|
||||
CABA14E82C9D7AA5002CB3B1 /* view */,
|
||||
CABA14E52C9D76F8002CB3B1 /* StartKeTangXieZuoViewController.h */,
|
||||
CABA14E62C9D76F8002CB3B1 /* StartKeTangXieZuoViewController.m */,
|
||||
CA8181862C9E58B100EE7E6E /* StartKTXZWenZhangViewController.h */,
|
||||
CA8181872C9E58B100EE7E6E /* StartKTXZWenZhangViewController.m */,
|
||||
);
|
||||
path = "课堂写作";
|
||||
sourceTree = "<group>";
|
||||
|
@ -865,6 +985,8 @@
|
|||
children = (
|
||||
CABA14E92C9D7AB2002CB3B1 /* StartKeTangXieZuoView.h */,
|
||||
CABA14EA2C9D7AB2002CB3B1 /* StartKeTangXieZuoView.m */,
|
||||
CA8181892C9E598300EE7E6E /* StartKTXZWenZhangView.h */,
|
||||
CA81818A2C9E598300EE7E6E /* StartKTXZWenZhangView.m */,
|
||||
);
|
||||
path = view;
|
||||
sourceTree = "<group>";
|
||||
|
@ -946,6 +1068,10 @@
|
|||
CB489E082744A0BC00DA044A /* UIDevice+PGJ.m */,
|
||||
CB489DEE2744A0BC00DA044A /* YQLocationTransform.h */,
|
||||
CB489E0B2744A0BC00DA044A /* YQLocationTransform.m */,
|
||||
CA81818C2C9E60B100EE7E6E /* ViewLable.h */,
|
||||
CA81818D2C9E60B100EE7E6E /* ViewLable.m */,
|
||||
CA8181A52C9EAB8700EE7E6E /* PGJUILabel.h */,
|
||||
CA8181A62C9EAB8700EE7E6E /* PGJUILabel.m */,
|
||||
);
|
||||
path = Other;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1834,6 +1960,7 @@
|
|||
CABA14CB2C9D4395002CB3B1 /* GTMBases64.m in Sources */,
|
||||
CB489FA02744A0BD00DA044A /* UIView+ZJFrame.m in Sources */,
|
||||
CABA148D2C9D4312002CB3B1 /* PublicNetWorkManager.m in Sources */,
|
||||
CA81818B2C9E598300EE7E6E /* StartKTXZWenZhangView.m in Sources */,
|
||||
CB489FA72744A0BD00DA044A /* ZJScrollPageView.m in Sources */,
|
||||
CB489F942744A0BD00DA044A /* AreaprovinceListModel.m in Sources */,
|
||||
CABA14B02C9D4318002CB3B1 /* UserAccountModel.m in Sources */,
|
||||
|
@ -1853,6 +1980,7 @@
|
|||
CB489FB72744A0BD00DA044A /* UIAlertController+Blocks.m in Sources */,
|
||||
CABA14612C9D0B1C002CB3B1 /* ShouYeViewController.m in Sources */,
|
||||
CABA14B62C9D4318002CB3B1 /* UserShareModel.m in Sources */,
|
||||
CA8181A12C9E9CC500EE7E6E /* StartKTXZPGDetailView.m in Sources */,
|
||||
CABA14B42C9D4318002CB3B1 /* UserLoginModel.m in Sources */,
|
||||
CB489FCC2744A0BD00DA044A /* ConfigireSetting.m in Sources */,
|
||||
CABA14CC2C9D4395002CB3B1 /* NSData+PEHIExtension.m in Sources */,
|
||||
|
@ -1866,10 +1994,14 @@
|
|||
CABA14B52C9D4318002CB3B1 /* UserModel.m in Sources */,
|
||||
CB489FA92744A0BD00DA044A /* UIView+XLExtension.m in Sources */,
|
||||
CB489F512744A0BD00DA044A /* PGJSearchView.m in Sources */,
|
||||
CA8181B02C9EB96000EE7E6E /* StartKTXZPGDetailFJView.m in Sources */,
|
||||
CB489F7A2744A0BD00DA044A /* FSTextView.m in Sources */,
|
||||
CABA14B32C9D4318002CB3B1 /* UserConfigModel.m in Sources */,
|
||||
CA8181992C9E77FB00EE7E6E /* DownJDLoadView.m in Sources */,
|
||||
CB489F6F2744A0BD00DA044A /* XLAlertView.m in Sources */,
|
||||
CA81819D2C9E9C6600EE7E6E /* StartKTXZPGDetailViewController.m in Sources */,
|
||||
CB489FA32744A0BD00DA044A /* ZJContentView.m in Sources */,
|
||||
CA8181922C9E748500EE7E6E /* StartKTXZPGViewController.m in Sources */,
|
||||
CB489F6C2744A0BD00DA044A /* RadianDisView.m in Sources */,
|
||||
CABA14AE2C9D4318002CB3B1 /* OrderInfoModel.m in Sources */,
|
||||
CB489F502744A0BD00DA044A /* Tools.m in Sources */,
|
||||
|
@ -1883,6 +2015,9 @@
|
|||
CB489F662744A0BD00DA044A /* DAYUtils.m in Sources */,
|
||||
CB489F6A2744A0BD00DA044A /* UILabel+PGGLabText.m in Sources */,
|
||||
CB489F772744A0BD00DA044A /* WYCamaImageTools.m in Sources */,
|
||||
CA8181B62C9ECB5800EE7E6E /* StartKTXZPGDetailFJItemView.m in Sources */,
|
||||
CA8181A72C9EAB8700EE7E6E /* PGJUILabel.m in Sources */,
|
||||
CA81818E2C9E60B100EE7E6E /* ViewLable.m in Sources */,
|
||||
CB489FB42744A0BD00DA044A /* CXDatePickerView.m in Sources */,
|
||||
CB489F7B2744A0BD00DA044A /* UIImageView+VideoImageView.m in Sources */,
|
||||
CB489F602744A0BD00DA044A /* GETCorelLocation.m in Sources */,
|
||||
|
@ -1893,6 +2028,7 @@
|
|||
CB489F922744A0BD00DA044A /* IndexGetNewsModel.m in Sources */,
|
||||
CABA14E02C9D6F30002CB3B1 /* RoleTreeModel.m in Sources */,
|
||||
CB489F4F2744A0BD00DA044A /* AddressListAlterViewTableViewCell.m in Sources */,
|
||||
CA8181A42C9EA07300EE7E6E /* StartKTXZPGDetailTabView.m in Sources */,
|
||||
CABA14E32C9D707E002CB3B1 /* TextbookInfoModel.m in Sources */,
|
||||
CB489F792744A0BD00DA044A /* UIApplication+Visible.m in Sources */,
|
||||
CB489FD72744A30000DA044A /* PGGMoviePlayer.m in Sources */,
|
||||
|
@ -1916,6 +2052,7 @@
|
|||
CB489F932744A0BD00DA044A /* PutImageModel.m in Sources */,
|
||||
CB489F7D2744A0BD00DA044A /* OpenSSLWrapper.m in Sources */,
|
||||
CB489FAB2744A0BD00DA044A /* XLZoomingScrollView.m in Sources */,
|
||||
CA8181962C9E751700EE7E6E /* StartKTXZPGView.m in Sources */,
|
||||
CB489F752744A0BD00DA044A /* NavgationController.m in Sources */,
|
||||
CB489F682744A0BD00DA044A /* DAYCalendarView.m in Sources */,
|
||||
CABA14AD2C9D4318002CB3B1 /* OrderInfoListModel.m in Sources */,
|
||||
|
@ -1929,10 +2066,12 @@
|
|||
CB489FB82744A0BD00DA044A /* HXLoadingHUD.m in Sources */,
|
||||
CABA14D12C9D4961002CB3B1 /* StartGNView.m in Sources */,
|
||||
CB489FA82744A0BD00DA044A /* XLProgressView.m in Sources */,
|
||||
CA8181AD2C9EB95700EE7E6E /* StartKTXZPGDetailZHView.m in Sources */,
|
||||
CB489F802744A0BD00DA044A /* NSString+Base64.m in Sources */,
|
||||
CB489FD82744A30000DA044A /* PGGMovieLineSetView.m in Sources */,
|
||||
CB489F4D2744A0BD00DA044A /* AddressListAlterView.m in Sources */,
|
||||
CB489FA22744A0BD00DA044A /* UIViewController+ZJScrollPageController.m in Sources */,
|
||||
CA8181882C9E58B100EE7E6E /* StartKTXZWenZhangViewController.m in Sources */,
|
||||
CB489F5A2744A0BD00DA044A /* YQLocationTransform.m in Sources */,
|
||||
CB489F782744A0BD00DA044A /* NSBundle+DAUtils.m in Sources */,
|
||||
CB489F4C2744A0BD00DA044A /* AddressListUrlAlterView.m in Sources */,
|
||||
|
@ -1952,6 +2091,7 @@
|
|||
CB489F562744A0BD00DA044A /* amrFileCodec.m in Sources */,
|
||||
CABA14822C9D1D0A002CB3B1 /* TextHeaderCollectionViewCell.m in Sources */,
|
||||
CB489F572744A0BD00DA044A /* AudioRecored.m in Sources */,
|
||||
CA8181B32C9EB96F00EE7E6E /* StartKTXZPGDetailXZView.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
Binary file not shown.
|
@ -3,116 +3,4 @@
|
|||
uuid = "41EC793F-022E-49E6-A76A-A910FEF0483C"
|
||||
type = "0"
|
||||
version = "2.0">
|
||||
<Breakpoints>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "6E30908F-C99A-4DF7-91AF-786CDB51EF6D"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "ProductApp/ProductMain/NetWorkManager/NetWorkManager.m"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "80"
|
||||
endingLineNumber = "80"
|
||||
landmarkName = "+requestExampleTextbookInfoData:role_id:pid:Callback:"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "D6799B8E-E195-4F66-A212-E247BE50C3E3"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "ProductApp/ProductMain/引导/课堂写作/view/StartKeTangXieZuoView.m"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "189"
|
||||
endingLineNumber = "189"
|
||||
landmarkName = "-drawInfoView"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "974ECB6D-A4AA-482F-A467-4C973D39D840"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "ProductApp/ProductMain/引导/课堂写作/view/StartKeTangXieZuoView.m"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "167"
|
||||
endingLineNumber = "167"
|
||||
landmarkName = "-drawInfoView"
|
||||
landmarkType = "7">
|
||||
<Locations>
|
||||
<Location
|
||||
uuid = "974ECB6D-A4AA-482F-A467-4C973D39D840 - 24aa26b3e1595c4d"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
symbolName = "-[StartKeTangXieZuoView drawInfoView]"
|
||||
moduleName = "ProductApp"
|
||||
usesParentBreakpointCondition = "Yes"
|
||||
urlString = "file:///Users/gongzuo/Desktop/%E5%85%AC%E5%8F%B8GIT/%E6%9E%81%E5%85%89%E5%86%99%E4%BD%9C/ProductApp/ProductApp/ProductMain/%E5%BC%95%E5%AF%BC/%E8%AF%BE%E5%A0%82%E5%86%99%E4%BD%9C/view/StartKeTangXieZuoView.m"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "167"
|
||||
endingLineNumber = "167">
|
||||
</Location>
|
||||
<Location
|
||||
uuid = "974ECB6D-A4AA-482F-A467-4C973D39D840 - 3fb8e11c6301110d"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
symbolName = "__37-[StartKeTangXieZuoView drawInfoView]_block_invoke_4"
|
||||
moduleName = "ProductApp"
|
||||
usesParentBreakpointCondition = "Yes"
|
||||
urlString = "file:///Users/gongzuo/Desktop/%E5%85%AC%E5%8F%B8GIT/%E6%9E%81%E5%85%89%E5%86%99%E4%BD%9C/ProductApp/ProductApp/ProductMain/%E5%BC%95%E5%AF%BC/%E8%AF%BE%E5%A0%82%E5%86%99%E4%BD%9C/view/StartKeTangXieZuoView.m"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "168"
|
||||
endingLineNumber = "168">
|
||||
</Location>
|
||||
</Locations>
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "656A30EF-4FF3-413F-BAD2-B0459E8ADFD1"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "ProductApp/ProductMain/引导/课堂写作/view/StartKeTangXieZuoView.m"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "76"
|
||||
endingLineNumber = "76"
|
||||
landmarkName = "-initWithFrame:"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "EEDC1596-54F4-43CA-B45E-50F3FA65E2F9"
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "ProductApp/ProductMain/引导/课堂写作/view/StartKeTangXieZuoView.m"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "60"
|
||||
endingLineNumber = "60"
|
||||
landmarkName = "-initWithFrame:"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
</Breakpoints>
|
||||
</Bucket>
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
#import "NavgationController.h"
|
||||
#import "StartViewController.h"
|
||||
|
||||
|
||||
#import "StartKTXZPGDetailViewController.h"
|
||||
|
||||
@interface AppDelegate ()
|
||||
|
||||
@end
|
||||
|
@ -30,7 +33,7 @@
|
|||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
self.window.backgroundColor = [UIColor whiteColor];
|
||||
|
||||
[self.window setRootViewController:[[NavgationController alloc] initWithRootViewController:[StartViewController new]]];
|
||||
[self.window setRootViewController:[[NavgationController alloc] initWithRootViewController:[StartKTXZPGDetailViewController new]]];
|
||||
|
||||
[self.window makeKeyAndVisible];
|
||||
|
||||
|
|
22
ProductApp/ProductApp/Assets.xcassets/引导页/yd_detail_jy_l.imageset/Contents.json
vendored
Normal file
22
ProductApp/ProductApp/Assets.xcassets/引导页/yd_detail_jy_l.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "切图 206@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "切图 206@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
ProductApp/ProductApp/Assets.xcassets/引导页/yd_detail_jy_l.imageset/切图 206@2x.png
vendored
Normal file
BIN
ProductApp/ProductApp/Assets.xcassets/引导页/yd_detail_jy_l.imageset/切图 206@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
ProductApp/ProductApp/Assets.xcassets/引导页/yd_detail_jy_l.imageset/切图 206@3x.png
vendored
Normal file
BIN
ProductApp/ProductApp/Assets.xcassets/引导页/yd_detail_jy_l.imageset/切图 206@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
21
ProductApp/ProductApp/Assets.xcassets/引导页/yd_drawTextB.imageset/Contents.json
vendored
Normal file
21
ProductApp/ProductApp/Assets.xcassets/引导页/yd_drawTextB.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "WechatIMG9.jpg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
ProductApp/ProductApp/Assets.xcassets/引导页/yd_drawTextB.imageset/WechatIMG9.jpg
vendored
Normal file
BIN
ProductApp/ProductApp/Assets.xcassets/引导页/yd_drawTextB.imageset/WechatIMG9.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.5 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue