UIView को फिर से तैयार करने के लिए सबसे मजबूत तरीका क्या है?


117

मेरे पास मदों की एक सूची के साथ एक UITableView है। एक आइटम का चयन एक दृश्य नियंत्रक को धक्का देता है जो फिर निम्नलिखित करने के लिए आगे बढ़ता है। विधि से देखेंडीडलॉड I डेटा के लिए URLRequest को बंद कर देता है जो कि मेरे उप-साक्षात्कारों द्वारा आवश्यक है - एक UIView उपवर्ग जिसमें ओवरराइड ओवरराइड है। जब डेटा क्लाउड से आता है तो मैं अपने दृश्य पदानुक्रम का निर्माण शुरू करता हूं। प्रश्न में उप-वर्ग डेटा को पारित कर देता है और यह ड्राअरक्ट विधि है जिसमें अब वह सब कुछ है जो उसे प्रस्तुत करने की आवश्यकता है।

परंतु।

क्योंकि मैं स्पष्ट रूप से आह्वान नहीं करता हूं - कोको-टच संभालता है कि - मेरे पास कोको-टच को सूचित करने का कोई तरीका नहीं है कि मैं वास्तव में चाहता हूं, वास्तव में इस यूइवेइ सबक्लास को प्रस्तुत करना चाहते हैं। कब? अब अच्छा होगा!

मैंने कोशिश की है [myView setNeedsDisplay]। यह थोड़ा कभी-कभी काम करता है। बहुत धब्बेदार।

मैं इसके लिए घंटों और घंटों कुश्ती कर रहा हूं। क्या कोई व्यक्ति जो मुझे एक रॉक सॉलिड, गारंटीकृत अप्रोच के साथ एक यूआईईवाईई-रेंडर प्रस्तुत करने की गारंटी दे सकता है।

यहाँ कोड का स्निपेट है जो दृश्य को डेटा खिलाता है:

// Create the subview
self.chromosomeBlockView = [[[ChromosomeBlockView alloc] initWithFrame:frame] autorelease];

// Set some properties
self.chromosomeBlockView.sequenceString     = self.sequenceString;
self.chromosomeBlockView.nucleotideBases    = self.nucleotideLettersDictionary;

// Insert the view in the view hierarchy
[self.containerView          addSubview:self.chromosomeBlockView];
[self.containerView bringSubviewToFront:self.chromosomeBlockView];

// A vain attempt to convince Cocoa-Touch that this view is worthy of being displayed ;-)
[self.chromosomeBlockView setNeedsDisplay];

चीयर्स, डौग

जवाबों:


193

गारंटी, रॉक सॉलिड तरीका है एक मजबूर UIViewकरने के लिए फिर से प्रस्तुत करना है [myView setNeedsDisplay]। यदि आप इससे परेशान हैं, तो आप इन मुद्दों में से एक में भाग लेने की संभावना रखते हैं:

  • इससे पहले कि आपके पास वास्तव में डेटा हो, या आप -drawRect:किसी चीज़ को ओवर-कैशिंग कर रहे हैं।

  • जब आप इस पद्धति को कॉल करते हैं, तो आप उस समय को देखने की अपेक्षा कर रहे हैं। कोको आरेखण प्रणाली का उपयोग करके जानबूझकर "अभी इस बहुत दूसरे को खींचने" की मांग करने का कोई तरीका नहीं है। यह पूरे दृश्य कंपोज़िटिंग सिस्टम को बाधित करेगा, कचरा प्रदर्शन और संभवतः सभी प्रकार की कलाकृतियों का निर्माण करेगा। केवल कहने के तरीके हैं "इसे अगले ड्रॉ चक्र में खींचने की आवश्यकता है।"

यदि आपको आवश्यकता है "कुछ तर्क, ड्रा, कुछ और तर्क," तो आपको "कुछ और तर्क" को एक अलग विधि में -performSelector:withObject:afterDelay:डालना होगा और 0. की देरी से उपयोग करके इसे लागू करना होगा। यह "कुछ और तर्क" डाल देगा। अगला ड्रा चक्र। इस तरह के कोड के उदाहरण के लिए इस प्रश्न को देखें , और एक ऐसा मामला जहां इसकी आवश्यकता हो सकती है (हालांकि आमतौर पर कोड को जटिल करने के बाद यदि संभव हो तो अन्य समाधानों के लिए देखना सबसे अच्छा है)।

अगर आपको नहीं लगता कि चीजें खींची जा रही हैं, तो एक ब्रेकपॉइंट -drawRect:लगाएं और देखें कि आपको कब बुलाया जा रहा है। यदि आप कॉल कर रहे हैं -setNeedsDisplay, लेकिन -drawRect:अगले ईवेंट लूप में नहीं बुलाया जा रहा है, तो अपने दृश्य पदानुक्रम में खोदें और सुनिश्चित करें कि आप कहीं बाहर जाने का प्रयास नहीं कर रहे हैं। मेरे अनुभव में अति-चतुराई, खराब ड्राइंग का # 1 कारण है। जब आपको लगता है कि आप सबसे अच्छा जानते हैं कि सिस्टम को कैसे करना है, जो आप चाहते हैं, तो आप इसे आसानी से प्राप्त कर सकते हैं।


रोब, यहाँ मेरी जाँच सूची है। 1) क्या मेरे पास डेटा है? हाँ। मैं एक विधि में दृश्य बनाता हूं - HideSpinner - कनेक्शन पर मुख्य थ्रेड से बुलायाडिफ़िनशेलडिंग: इस प्रकार: [आत्म performSelectorOnMainThread: @selector (hideSpinner )Object: nil WaitUntilDone: NO]; 2) मुझे तुरन्त ड्राइंग की आवश्यकता नहीं है। मुझे बस इसकी जरूरत है। आज। वर्तमान में, यह पूरी तरह से यादृच्छिक है, और मेरे नियंत्रण से बाहर है। [myView setNeedsDisplay] पूरी तरह से अविश्वसनीय है। मैं यहाँ तक देखने के लिए गया है [myView setNeedsDisplay] को देखने के लिए। Nuthin '। कोको बस मुझे अनदेखा करता है। कष्टदायक !!
दुगला

1
मैंने पाया है कि इस दृष्टिकोण के साथ अक्सर setNeedsDisplayऔर वास्तविक कॉल के बीच देरी होती है drawRect:। हालांकि कॉल की विश्वसनीयता यहां है, मैं इसे "सबसे मजबूत" समाधान नहीं कहूंगा- एक मजबूत ड्राइंग समाधान, सिद्धांत रूप में, ड्रॉ की मांग करने वाले क्लाइंट कोड पर वापस लौटने से पहले तुरंत सभी ड्राइंग करना चाहिए।
स्लिप डी। थॉम्पसन

1
मैंने अधिक विवरण के साथ आपके उत्तर पर नीचे टिप्पणी की। ड्राइंग सिस्टम को आदेश से बाहर चलाने के लिए मजबूर करने की कोशिश करने के तरीकों को स्पष्ट रूप से दस्तावेज कहते हैं कि कॉल नहीं करना मजबूत नहीं है। सबसे अच्छा यह अपरिभाषित व्यवहार है। सबसे अधिक संभावना है कि यह प्रदर्शन और गुणवत्ता को कम करेगा। सबसे खराब स्थिति में यह गतिरोध या दुर्घटना हो सकती है।
रोब नेपियर

1
यदि आपको लौटने से पहले आकर्षित करने की आवश्यकता है, तो एक छवि संदर्भ में आकर्षित करें (जो कैनवास की तरह ही काम करता है, जिसकी कई लोग उम्मीद करते हैं)। लेकिन कंपोज़िटिंग सिस्टम को ट्रिक करने की कोशिश न करें। यह उच्च गुणवत्ता वाले ग्राफिक्स प्रदान करने के लिए डिज़ाइन किया गया है, जो बहुत कम संसाधन ओवरहेड हैं। इसका एक भाग रन लूप के अंत में ड्राइंग स्टेप्स को समेटना है।
रोब नेपियर

1
@RobNapier मुझे समझ नहीं आता कि आप इतने रक्षात्मक क्यों हो रहे हैं। कृपया दोबारा जांच करें; कोई एपीआई उल्लंघन नहीं है (हालांकि यह आपके सुझाव के आधार पर साफ किया गया है, मैं तर्क देता हूं कि किसी की खुद की पद्धति का उल्लंघन नहीं होगा), न ही गतिरोध या दुर्घटना की संभावना। यह भी एक "चाल" नहीं है; यह सामान्य तरीके से कैलेयर के सेटनएड्सप्ले / डिस्प्ले का उपयोग करता है। इसके अलावा, मैं इसे काफी समय से क्वार्ट्ज के साथ GLKView / OpenGL के समानांतर तरीके से आकर्षित करने के लिए उपयोग कर रहा हूं; यह आपके द्वारा सूचीबद्ध समाधान से सुरक्षित, स्थिर और तेज़ साबित हुआ है। यदि आप मुझ पर विश्वास नहीं करते हैं, तो कोशिश करें। आपके पास यहां खोने के लिए कुछ नहीं है।
स्लिप डी। थॉम्पसन

52

मुझे सेटनएड्स डिसेप्ले और ड्रॉक्ट्रेक्ट: (5 सेकंड) के बीच एक बड़ी देरी की समस्या थी। यह पता चला कि मैंने setNeedsDisplay को मुख्य धागे से अलग थ्रेड में बुलाया। इस कॉल को मुख्य धागे में ले जाने के बाद देरी हो गई।

उम्मीद है यह कुछ मदद करे।


यह निश्चित रूप से मेरी बग था। मैं इस धारणा के तहत था कि मैं मुख्य धागे पर चल रहा था, लेकिन यह तब तक नहीं था जब तक कि मैंने एनएसस्टॉग्रेडेड एनआईएसह्रेडेड नहीं देखा। मुझे एहसास हुआ कि एक कोने का मामला था जहां मैं मुख्य धागे पर परत में बदलाव नहीं कर रहा था। मुझे अपने बालों को बाहर निकालने से बचाने के लिए धन्यवाद!
श्री टी।

14

मनी-बैक गारंटीकृत, प्रबलित-ठोस-ठोस तरीके से समकालिक रूप से आकर्षित करने के लिए मजबूर करना (कॉलिंग कोड पर लौटने से पहले)CALayer आपके UIViewउपवर्ग के साथ बातचीत को कॉन्फ़िगर करना है ।

अपने यूविवि उपवर्ग में, एक displayNow()विधि बनाएं जो परत को " प्रदर्शन के लिए सेट " करने के लिए कहता है, फिर " ऐसा करने के लिए ":

तीव्र

/// Redraws the view's contents immediately.
/// Serves the same purpose as the display method in GLKView.
public func displayNow()
{
    let layer = self.layer
    layer.setNeedsDisplay()
    layer.displayIfNeeded()
}

उद्देश्य सी

/// Redraws the view's contents immediately.
/// Serves the same purpose as the display method in GLKView.
- (void)displayNow
{
    CALayer *layer = self.layer;
    [layer setNeedsDisplay];
    [layer displayIfNeeded];
}

एक ऐसी draw(_: CALayer, in: CGContext)विधि भी लागू करें जो आपके निजी / आंतरिक ड्राइंग विधि को बुलाएगी (जो कि हर UIViewएक के बाद काम करती है CALayerDelegate) :

तीव्र

/// Called by our CALayer when it wants us to draw
///     (in compliance with the CALayerDelegate protocol).
override func draw(_ layer: CALayer, in context: CGContext)
{
    UIGraphicsPushContext(context)
    internalDraw(self.bounds)
    UIGraphicsPopContext()
}

उद्देश्य सी

/// Called by our CALayer when it wants us to draw
///     (in compliance with the CALayerDelegate protocol).
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
{
    UIGraphicsPushContext(context);
    [self internalDrawWithRect:self.bounds];
    UIGraphicsPopContext();
}

और अपनी कस्टम internalDraw(_: CGRect)विधि बनाएं , साथ ही असफल-सुरक्षित draw(_: CGRect):

तीव्र

/// Internal drawing method; naming's up to you.
func internalDraw(_ rect: CGRect)
{
    // @FILLIN: Custom drawing code goes here.
    //  (Use `UIGraphicsGetCurrentContext()` where necessary.)
}

/// For compatibility, if something besides our display method asks for draw.
override func draw(_ rect: CGRect) {
    internalDraw(rect)
}

उद्देश्य सी

/// Internal drawing method; naming's up to you.
- (void)internalDrawWithRect:(CGRect)rect
{
    // @FILLIN: Custom drawing code goes here.
    //  (Use `UIGraphicsGetCurrentContext()` where necessary.)
}

/// For compatibility, if something besides our display method asks for draw.
- (void)drawRect:(CGRect)rect {
    [self internalDrawWithRect:rect];
}

और अब बस कॉल करें myView.displayNow()जब भी आपको वास्तव में आकर्षित करने की आवश्यकता हो (जैसे कि CADisplayLinkकॉलबैक से) । हमारे displayNow()विधि बता देंगे CALayerकरने के लिए displayIfNeeded()है, जो तुल्यकालिक हमारे में आपको वापस कॉल करेगा draw(_:,in:)और में ड्राइंग करना internalDraw(_:), क्या पर जाने से पहले संदर्भ में तैयार है के साथ दृश्य को अद्यतन करने।


यह दृष्टिकोण @ RobNapier के उपरोक्त के समान है, लेकिन displayIfNeeded()इसके अतिरिक्त कॉल करने का लाभ है setNeedsDisplay(), जो इसे तुल्यकालिक बनाता है।

यह संभव है क्योंकि CALayers UIViewdo- लेयर्स से अधिक ड्रॉइंग फंक्शनालिटी को उजागर करता है- लेयर्स व्यू की तुलना में लो-लेवल होते हैं और लेआउट के भीतर अत्यधिक-कंफर्टेबल ड्राइंग के उद्देश्य से स्पष्ट रूप से डिजाइन किए जाते हैं, और (Cocoa में कई चीजों की तरह) फ्लेक्सिबल तरीके से डिजाइन किए जाते हैं। एक मूल वर्ग के रूप में, या एक प्रतिनिधि के रूप में, या अन्य ड्राइंग सिस्टम के लिए एक पुल के रूप में, या बस अपने दम पर)। CALayerDelegateप्रोटोकॉल का उचित उपयोग यह सब संभव बनाता है।

कोर एनिमेशन प्रोग्रामिंग गाइडCALayer के सेटिंग अप लेयर ऑब्जेक्ट्स सेक्शन में एस के विन्यास के बारे में अधिक जानकारी पाई जा सकती है ।


ध्यान दें कि दस्तावेज़ के लिए drawRect:स्पष्ट रूप से कहा गया है "आपको इस पद्धति को कभी भी सीधे नहीं कहना चाहिए।" साथ ही, काइलेर displayस्पष्ट रूप से कहते हैं "इस पद्धति को सीधे मत कहो ।" यदि आप contentsसीधे लेयर्स पर सिंक्रोनाइज़ करना चाहते हैं तो इन नियमों का उल्लंघन करने की कोई आवश्यकता नहीं है। आप केवल परत के contentsकिसी भी समय आप चाहते हैं (यहां तक ​​कि पृष्ठभूमि के धागे पर) आकर्षित कर सकते हैं । बस उस के लिए दृश्य में एक सबलेयर जोड़ें। लेकिन यह स्क्रीन पर डालने से अलग है, जिसे सही कंपोजिंग समय तक इंतजार करना होगा।
रोब नेपियर

मैं कहता हूं कि एक सबलेयर को जोड़ने के बाद से डॉक्स एक UIView की परत के साथ सीधे खिलवाड़ करने के बारे में चेतावनी देता है contents("यदि परत ऑब्जेक्ट को किसी व्यू ऑब्जेक्ट से बांधा गया है, तो आपको इस संपत्ति की सामग्री को सीधे सेट करने से बचना चाहिए। विचारों और परतों के बीच का अंतर आमतौर पर परिणाम देता है। बाद में अपडेट के दौरान इस संपत्ति की सामग्री को बदलने के दृश्य में। ") मैं विशेष रूप से इस दृष्टिकोण की सिफारिश नहीं कर रहा हूं; समय से पहले ड्राइंग प्रदर्शन और ड्राइंग की गुणवत्ता को कमजोर करता है। लेकिन अगर आपको किसी कारण से इसकी आवश्यकता है, तो contentsइसे कैसे प्राप्त करें।
रोब नेपियर

@RobNapier प्वाइंट drawRect:सीधे कॉलिंग के साथ लिया गया । इस तकनीक को प्रदर्शित करना आवश्यक नहीं था, और इसे ठीक कर दिया गया है।
स्लिप डी। थॉम्पसन

@RobNapier जिस contentsतकनीक के लिए आप सुझाव दे रहे हैं ... मोहक लगती है। मैंने शुरू में ऐसा कुछ करने की कोशिश की, लेकिन यह काम करने के लिए नहीं मिल सका, और उपरोक्त समाधान को बहुत कम कोड के रूप में पाया, बिना किसी कारण के केवल प्रदर्शन करने वाला नहीं। हालांकि, अगर आपके पास contentsदृष्टिकोण के लिए एक कार्यशील समाधान है , तो मुझे इसे पढ़ने में दिलचस्पी होगी (ऐसा कोई कारण नहीं है कि आपके पास इस प्रश्न के दो उत्तर नहीं हो सकते, ठीक है?)
स्लिप डी। थॉम्पसन

@RobNapier ध्यान दें: यह कुछ भी नहीं है के साथ है CALayer display। यह UIView उपवर्ग में सिर्फ एक कस्टम सार्वजनिक विधि है, ठीक वैसे ही जैसे GLKView (एक और UIView उपवर्ग, और केवल Apple-लिखित एक जो मुझे पता है कि DRAW अब! कार्यक्षमता की मांग करता है) में किया जाता है।
स्लिप डी। थॉम्पसन

5

मुझे एक ही समस्या थी, और SO या Google के सभी समाधानों ने मेरे लिए काम नहीं किया। आमतौर पर, setNeedsDisplayकाम करता है, लेकिन जब यह नहीं होता है ...
मैंने setNeedsDisplayहर संभव धागे और सामान से हर संभव तरीके से कॉल करने की कोशिश की है - फिर भी कोई सफलता नहीं मिली। हम जानते हैं, जैसा कि रोब ने कहा, कि

"इसे अगले ड्रॉ चक्र में खींचने की आवश्यकता है।"

लेकिन किसी कारण से यह इस बार नहीं होगा। और एकमात्र समाधान जो मैंने पाया है वह कुछ समय के बाद मैन्युअल रूप से कॉल कर रहा है, कुछ भी करने के लिए जो ड्रॉ को दूर करता है, इस तरह से:

dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 
                                        (int64_t)(0.005 * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
    [viewToRefresh setNeedsDisplay];
});

यह एक अच्छा समाधान है अगर आपको वास्तव में अक्सर बार फिर से देखने की आवश्यकता नहीं है। अन्यथा, यदि आप कुछ मूविंग (एक्शन) सामान कर रहे हैं, तो आमतौर पर सिर्फ कॉल करने में कोई समस्या नहीं है setNeedsDisplay

मुझे उम्मीद है कि यह किसी ऐसे व्यक्ति की मदद करेगा जो वहां खो गया है, जैसे मैं था।


0

वैसे मुझे पता है कि यह एक बड़ा बदलाव हो सकता है या आपकी परियोजना के लिए भी उपयुक्त नहीं होगा, लेकिन क्या आपने तब तक पुश प्रदर्शन नहीं करने पर विचार किया था जब तक आपके पास पहले से ही डेटा नहीं है ? इस तरह से आपको केवल एक बार दृश्य को आकर्षित करने की आवश्यकता है और उपयोगकर्ता का अनुभव भी बेहतर होगा - धक्का पहले से ही लोड हो जाएगा।

जिस तरह से आप ऐसा कर रहे हैं, UITableView didSelectRowAtIndexPathआप अतुल्यकालिक रूप से डेटा के लिए पूछते हैं। एक बार जब आप प्रतिक्रिया प्राप्त करते हैं, तो आप मैन्युअल रूप से सेगमेंट करते हैं और डेटा को अपने व्यू कॉन्ट्रोलर में पास करते हैं prepareForSegue। इस बीच आप कुछ गतिविधि संकेतक दिखाना चाह सकते हैं, साधारण लोडिंग संकेतक के लिए https://github.com/jdg/MBProgressHUD देखें

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.