YYText कैसे काम करता है

Home PDF

ऊपर दिखाया गया छाया प्रभाव निम्नलिखित कोड का उपयोग करके बनाया गया है:

आप देख सकते हैं कि YYTextShadow बनाया गया है, फिर इसे attributedString के yy_textShadow को सौंपा गया है, और फिर attributedString को YYLabel में सौंपा गया है, और अंत में YYLabel को UIView में जोड़कर प्रदर्शित किया गया है। yy_textShadow को ट्रैक करने पर पता चलता है कि मुख्य रूप से textShadow को NSAttributedString के attribute से बांधा गया है, जहां key YYTextShadowAttributeName है और value textShadow है, यानी पहले shadow को स्टोर किया जाता है और बाद में उपयोग किया जाता है। Shift + Command + J का उपयोग करके परिभाषा पर तेजी से जाएं:

यहाँ एक addAttribute है, जो NSAttributedString.h में परिभाषित है:

- (void)addAttribute:(NSString *)name value:(id)value range:(NSRange)range;

यह कोड Objective-C में एक मेथड को दर्शाता है जो किसी विशेष गुण (attribute) को एक निश्चित रेंज (range) में जोड़ने के लिए उपयोग किया जाता है। इसमें name पैरामीटर गुण का नाम, value पैरामीटर गुण का मान, और range पैरामीटर उस रेंज को दर्शाता है जहाँ यह गुण जोड़ा जाएगा।

यह बताता है कि इसमें किसी भी की-वैल्यू जोड़ी को असाइन किया जा सकता है। और YYTextShadowAttributeName की परिभाषा एक सामान्य स्ट्रिंग है, जिसका मतलब है कि पहले shadow की जानकारी को स्टोर किया जाता है, और फिर बाद में उपयोग किया जाता है। हम YYTextShadowAttributeName को ग्लोबली सर्च करते हैं।

फिर हम YYTextLayout में YYTextDrawShadow फ़ंक्शन पर आते हैं:

CGContextTranslateCTM का मतलब है कि यह एक Context के अंदर मूल बिंदु (origin) के निर्देशांक को बदलता है, इसलिए

CGContextTranslateCTM(context, point.x, point.y);

(यह कोड Objective-C में है और इसे अनुवादित नहीं किया जाता है। यह कोड CGContextTranslateCTM फ़ंक्शन का उपयोग करके ग्राफ़िक्स कॉन्टेक्स्ट को ट्रांसलेट करता है।)

यह कहा जा रहा है कि ड्रॉ किए गए कॉन्टेक्स्ट को point बिंदु पर ले जाना है। हम पहले यह समझ लेते हैं कि YYTextDrawShadow कहाँ कॉल किया गया है, और हमने पाया कि यह drawInContext में कॉल किया गया है।

drawInContext में, निम्नलिखित क्रम में ब्लॉक की सीमा रेखा खींची जाती है: पहले बॉर्डर, फिर पृष्ठभूमि बॉर्डर, छाया, अंडरलाइन, टेक्स्ट, एक्सेसरीज़, इनर शैडो, स्ट्राइकथ्रू, टेक्स्ट बॉर्डर, और अंत में डिबग लाइन।

तो आखिर drawInContext का उपयोग कहाँ किया गया है? आप देख सकते हैं कि इसमें एक पैरामीटर YYTextDebugOption है, इसलिए यह फ़ंक्शन निश्चित रूप से सिस्टम का कॉलबैक नहीं है, बल्कि YYText के अंदर से ही कॉल किया गया है।

Ctrl + 1 दबाने पर शॉर्टकट पॉप अप होता है, और यह देखा जाता है कि इसे चार स्थानों पर कॉल किया गया है।

drawInContext:size:debug अभी भी YYText का अपना कॉल है, क्योंकि debug का प्रकार YYTextDebugOption * है, जो YY का अपना है। newAsyncTask सिस्टम कॉल की तरह नहीं लगता, addAttachmentToView:layer: भी ऐसा ही है, इसलिए संभावना है कि यह drawRect: हो सकता है।

देखिए, दाईं ओर के त्वरित सहायता अनुभाग में विस्तृत व्याख्या है, और सहायता के नीचे यह भी बताया गया है कि यह UIView में परिभाषित है। अब YYTextContainerView को देखें, यह UIView को इनहेरिट करता है।

तो क्या YYLabel ने YYTextContainerView का उपयोग किया है? और फिर सिस्टम को YYTextContainerView में drawRect: को कॉल करके ड्रा करने दिया है?

अजीब बात है, YYLabel UIView को इनहेरिट करता है। इसलिए, YYText में दो चीज़ें होनी चाहिए! एक YYLabel और एक YYTextView, जैसे UILabel और UITextView। फिर हम पहले वाले YYLabel के newAsyncDisplayTask को फिर से देखेंगे।

बहुत लंबा है, बीच में YYTextLayout के drawInContext को कॉल किया गया है। newAsyncDisplayTask, यह कहाँ कॉल किया गया है?

दूसरी पंक्ति में कॉल किया गया है। इसलिए इसे सरलता से समझा जा सकता है कि YYLabel ने टेक्स्ट को ड्रॉ करने के लिए एसिंक्रोनस तरीके का उपयोग किया है। और _displayAsync को ऊपर के display द्वारा कॉल किया गया है। display के डॉक्यूमेंटेशन को देखने पर, यह कहा गया है कि सिस्टम उचित समय पर लेयर की सामग्री को अपडेट करने के लिए इसे कॉल करेगा, आपको इसे सीधे कॉल नहीं करना चाहिए। हम इसमें एक ब्रेकपॉइंट भी सेट कर सकते हैं।

display को CALayer के एक transaction में कॉल किया जाता है। Transaction का उपयोग शायद इसलिए किया जाता है ताकि बड़े पैमाने पर अपडेट को एक साथ किया जा सके, जिससे efficiency बढ़ सके। यह database में rollback की आवश्यकता की तरह नहीं लगता।

display 的系统文档中还提到,如果你想让你自己的 layer 以不同的方式绘制,你可以通过重写这个方法来实现自定义的绘制。

तो, हमारे पास थोड़ा सा विचार है। YYLabel ने UIView के display मेथड को ओवरराइड करके अपनी छाया और अन्य प्रभावों को एसिंक्रोनस तरीके से ड्रॉ करने का तरीका अपनाया है। छाया प्रभाव पहले YYLabel के attributedText में एट्रिब्यूट के रूप में सहेजे जाते हैं, और फिर display में ड्रॉ करते समय इन्हें निकाला जाता है। ड्रॉ करते समय सिस्टम के CoreGraphics फ्रेमवर्क का उपयोग किया जाता है।

कुछ विचारों को स्पष्ट करने के बाद, आपको पता चलेगा कि वास्तव में शक्तिशाली क्या है? एक तरफ इतने सारे प्रभावों और एसिंक्रोनस कॉल्स को व्यवस्थित करना है, और दूसरी तरफ CoreGraphics फ्रेमवर्क का गहराई से उपयोग करना है। इसलिए, पिछले कोड संगठन को समझने के बाद, हम CoreGraphics फ्रेमवर्क में गहराई से जाएंगे। देखें कि यह कैसे ड्रॉ किया जाता है।

चलिए फिर से YYTextDrawShadow पर वापस आते हैं।

यहां, CGContextSaveGState और CGContextRestoreGState ने ड्राइंग कोड के एक ब्लॉक को घेरा हुआ है। CGContextSaveGState का मतलब है कि वर्तमान ड्राइंग स्टेट की एक कॉपी बनाकर उसे ड्राइंग स्टैक में डाल दिया जाए। हर ड्राइंग Context एक ड्राइंग स्टैक को मेन्टेन करता है। मुझे यह स्पष्ट नहीं है कि स्टैक के अंदर वास्तव में क्या होता है। फिलहाल इसे इस तरह समझें कि ड्राइंग Context से पहले CGContextSaveGState को कॉल करना है और ड्राइंग Context के बाद CGContextRestoreGState को कॉल करना है, ताकि बीच का ड्राइंग Context में प्रभावी ढंग से दिखाई दे। CGContextTranslateCTM Context को संबंधित स्थान पर ले जाता है। पहले point.x और point.y पर ले जाया जाता है, जो ड्राइंग का संबंधित स्थान है, लेकिन बाद में 0 और size.height पर ले जाने का कारण स्पष्ट नहीं है, इसे बाद में देखा जाएगा। फिर lines को निकाला जाता है और for लूप को एक्ज़ीक्यूट किया जाता है।

lines क्या है? यह YYTextLayout में (YYTextLayout *)layoutWithContainer:(YYTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range के दौरान असाइन किया जाता है।

फिर इस फ़ंक्शन की परिभाषा पर जाएं:

यह फ़ंक्शन बहुत लंबा है, 367 से 861 लाइन तक, 500 लाइन का कोड! इसके शुरुआत और अंत को देखकर, यह स्पष्ट है कि इसका उद्देश्य इन चरों को प्राप्त करना है। lines कैसे प्राप्त किया जाता है?

एक बड़े for लूप में, एक-एक करके line को lines में जोड़ा जा सकता है। तो lineCount कैसे प्राप्त होता है?

472वीं पंक्ति में एक framesetter ऑब्जेक्ट बनाया गया है, text पैरामीटर NSAttributedString है, फिर frameSetter ऑब्जेक्ट में एक CTFrameRef बनाया गया है, और फिर CTFrameRef से lines प्राप्त किया गया है। line वास्तव में क्या है? हम इस पर एक ब्रेकपॉइंट लगाएंगे।

पाया गया कि shadow शब्द का lineCount = 2 है, जो हमारी कल्पना के अक्षरों की संख्या नहीं है।

तो क्या अनुमान लगाया जा सकता है कि सफेद Shadow पूरी तरह से एक line है, और छाया भी एक line है?

YYText में कई उदाहरण हैं, जिनमें से केवल एक प्रभाव दिखाया गया है, और अन्य कोड को टिप्पणी में डाल दिया गया है। यह देखकर अजीब लगता है कि Shadow का lineCount = 2 है, और Multiple Shadows का lineCount भी 2 है, लेकिन Multiple Shadows में आंतरिक छाया (inner shadow) भी है, इसलिए यह 3 होना चाहिए?

CTLine के Apple डॉक्यूमेंटेशन को देखें, यह कहता है कि CTLine एक पंक्ति के टेक्स्ट को प्रतिनिधित्व करता है, और एक CTLine ऑब्जेक्ट में glyph runs का एक समूह होता है। तो यह सिर्फ साधारण पंक्तियों की संख्या है! ऊपर दिए गए ब्रेकपॉइंट स्क्रीनशॉट को देखें, जहां shadow का मान 2 था, क्योंकि इसका टेक्स्ट shadow\n\n था। ध्यान दें कि \n\n को जानबूझकर जोड़ा गया था, ताकि यह सुंदर दिखे।

इसलिए shadow\n\n दो पंक्तियों का टेक्स्ट है। CTLine वही है जिसे हम आमतौर पर एक पंक्ति कहते हैं। फिर हमारे lineCount पर वापस जाते हैं:

यहां हमें CTLines सरणी मिलती है, उसमें से तत्वों की संख्या प्राप्त करते हैं, और यदि lineCount 0 से अधिक है तो प्रत्येक पंक्ति के निर्देशांक मूल बिंदु प्राप्त करते हैं। ठीक है, अब हमारे पास lineCount है, आइए अब for लूप पर नजर डालते हैं।

ctLines सरणी से CTLine प्राप्त करें, फिर YYTextLine ऑब्जेक्ट प्राप्त करें, और उसे lines सरणी में जोड़ें। फिर line के फ्रेम की गणना करें। YYTextLine का कंस्ट्रक्टर बहुत सरल है, पहले स्थिति, लंबवत टाइपसेटिंग, और CTLine ऑब्जेक्ट को सहेजता है:

lines को समझने के बाद, हम पहले वाले YYTextDrawShadow पर वापस जाते हैं:

यह कोड अब सरल हो गया है। पहले पंक्तियों की संख्या प्राप्त करें, उन्हें ट्रैवर्स करें, फिर GlyphRuns सरणी प्राप्त करें, और उसे ट्रैवर्स करें। GlyphRun को एक प्राइमिटिव या ड्रॉइंग यूनिट के रूप में समझा जा सकता है। फिर उससे attributes सरणी प्राप्त करें, हमारे पहले YYTextShadowAttributeName का उपयोग करके, हम शुरू में असाइन किए गए shadow को प्राप्त करें, और फिर छाया बनाना शुरू करें:

एक while लूप, जो लगातार उप-छाया बनाता है। CGContextSetShadowWithColor को कॉल करके छाया का विस्थापन, त्रिज्या और रंग सेट किया जाता है। फिर YYTextDrawRun को कॉल करके वास्तविक रूप से ड्रॉ किया जाता है। YYTextDrawRun को तीन स्थानों पर कॉल किया जाता है:

यह आंतरिक छाया, टेक्स्ट छाया और टेक्स्ट को ड्रॉ करने के लिए उपयोग किया जाता है। यह दर्शाता है कि यह एक सामान्य विधि है, जिसका उपयोग Run ऑब्जेक्ट को ड्रॉ करने के लिए किया जाता है।

शुरुआत में टेक्स्ट का ट्रां्सफॉर्म मैट्रिक्स प्राप्त करें, और runTextMatrixIsID का उपयोग करके जांचें कि क्या यह अपरिवर्तित है। यदि यह वर्टिकल टाइपसेटिंग नहीं है या ग्राफिक्स ट्रां्सफॉर्मेशन सेट नहीं है, तो सीधे ड्रॉ करें। CTRunDraw को कॉल करके run ऑब्जेक्ट को ड्रॉ करें। फिर ब्रेकपॉइंट पर ध्यान दें, और पाएं कि शुरुआती शैडो को ड्रॉ करते समय केवल if ब्लॉक में प्रवेश किया गया था, else ब्लॉक में नहीं।

तो हमारी छाया ड्राइंग यहीं समाप्त होती है!

संक्षेप में, YYLabel पहले छाया जैसे प्रभावों को attributedText के attributes में सहेजता है, UIView की display विधि को ओवरराइड करता है, और display में एसिंक्रोनस ड्राइंग करता है। CoreText फ्रेमवर्क का उपयोग करके CTLine और CTRun ऑब्जेक्ट प्राप्त करता है, CTRun से attributes प्राप्त करता है, और फिर attributes में मौजूद विभिन्न गुणों के आधार पर CoreGraphics फ्रेमवर्क का उपयोग करके CTRun ऑब्जेक्ट को Context में ड्रा करता है।

समझ अभी भी पर्याप्त नहीं है, बाद में फिर से पढ़ने के लिए आऊंगा। अनजाने में यह सोचकर आश्चर्य होता है कि YY वास्तव में बहुत शक्तिशाली है! आज मैंने अपने विचारों को व्यवस्थित किया, और खुद को कोड लिखते और पढ़ते हुए रखा, ताकि यह उबाऊ न हो, और साथ ही दूसरों के लिए संदर्भ के रूप में काम कर सके। अब सोने जाना होगा।


Back 2025.01.18 Donate