Cách mạnh mẽ nhất để buộc một UIView vẽ lại là gì?


116

Tôi có một UITableView với một danh sách các mục. Việc chọn một mục sẽ đẩy một viewContoder sau đó tiến hành các thao tác sau. từ phương thức viewDidLoad Tôi tắt URLRequest cho dữ liệu được yêu cầu bởi trong các cuộc phỏng vấn của tôi - một lớp con UIView với drawRect bị ghi đè. Khi dữ liệu đến từ đám mây, tôi bắt đầu xây dựng hệ thống phân cấp chế độ xem của mình. lớp con trong câu hỏi được truyền dữ liệu và phương thức drawRect hiện có mọi thứ nó cần để kết xuất.

Nhưng.

Bởi vì tôi không gọi drawRect một cách rõ ràng - Cacao-Touch xử lý điều đó - Tôi không có cách nào thông báo cho Cacao-Touch rằng tôi thực sự, thực sự muốn lớp con UIView này hiển thị. Khi nào? Bây giờ sẽ tốt thôi!

Tôi đã thử [myView setNeedsDisplay]. Loại này đôi khi hoạt động. Rất đốm.

Tôi đã vật lộn với điều này trong nhiều giờ. Ai đó có thể vui lòng cung cấp cho tôi một cách tiếp cận chắc chắn, vững chắc để buộc tái tạo UIView.

Đây là đoạn mã cung cấp dữ liệu cho chế độ xem:

// 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];

Chúc mừng, Doug

Câu trả lời:


192

Cách chắc chắn, chắc chắn để buộc UIViewphải kết xuất lại [myView setNeedsDisplay]. Nếu bạn gặp rắc rối với điều đó, có khả năng bạn đang gặp phải một trong những vấn đề sau:

  • Bạn đang gọi nó trước khi bạn thực sự có dữ liệu, hoặc bạn -drawRect:đang lưu quá nhiều thứ.

  • Bạn đang mong đợi khung nhìn sẽ được vẽ tại thời điểm bạn gọi phương thức này. Không có cách nào để yêu cầu "vẽ ngay thứ hai này" bằng cách sử dụng hệ thống vẽ Cacao. Điều đó sẽ phá vỡ toàn bộ hệ thống tổng hợp khung nhìn, hiệu suất rác và có khả năng tạo ra tất cả các loại tạo tác. Chỉ có cách để nói "điều này cần được rút ra trong chu kỳ bốc thăm tiếp theo".

Nếu cái bạn cần là "một số logic, vẽ, một số logic hơn", thì bạn cần đặt "một số logic hơn" trong một phương thức riêng biệt và gọi nó bằng cách sử dụng -performSelector:withObject:afterDelay:với độ trễ bằng 0. Điều đó sẽ đặt "một số logic hơn" sau chu kỳ vẽ tiếp theo. Xem câu hỏi này để biết ví dụ về loại mã đó và trường hợp có thể cần đến (mặc dù tốt nhất là tìm kiếm các giải pháp khác nếu có thể vì nó làm phức tạp mã).

Nếu bạn không nghĩ mọi thứ đang được rút ra, hãy đặt một điểm dừng -drawRect:và xem khi nào bạn được gọi. Nếu bạn đang gọi -setNeedsDisplay, nhưng -drawRect:không được gọi trong vòng lặp sự kiện tiếp theo, thì hãy đi sâu vào phân cấp chế độ xem của bạn và đảm bảo rằng bạn không cố gắng ở bên ngoài. Sự thông minh quá mức là nguyên nhân số 1 của bản vẽ xấu trong kinh nghiệm của tôi. Khi bạn nghĩ rằng bạn biết cách tốt nhất để lừa hệ thống thực hiện những gì bạn muốn, bạn thường khiến nó thực hiện chính xác những gì bạn không muốn.


Rob, đây là danh sách kiểm tra của tôi. 1) Tôi có dữ liệu không? Đúng. Tôi tạo khung nhìn trong một phương thức - hideSpinner - được gọi trên luồng chính từ ConnectionDidFinishLoading: do đó: [self biểu diễnSelectorOnMainThread: @selector (hideSpinner) withObject: nil WaitUntilDone: NO]; 2) Tôi không cần vẽ ngay lập tức. Tôi chỉ cần nó. Hôm nay. Hiện tại, nó hoàn toàn ngẫu nhiên, và nằm ngoài tầm kiểm soát của tôi. [myView setNeedsDisplay] hoàn toàn không đáng tin cậy. Tôi thậm chí đã đi xa đến mức gọi [myView setNeedDisplay] trong viewDidAppear :. Nuthin '. Ca cao chỉ đơn giản là bỏ qua tôi. Tức giận !!
dugla

1
Tôi đã thấy rằng với phương pháp này thường có độ trễ giữa setNeedsDisplaycuộc gọi thực tế và cuộc gọi drawRect:. Trong khi độ tin cậy của cuộc gọi là ở đây, tôi sẽ không gọi đây là giải pháp mạnh mẽ nhất của Google. Về lý thuyết, hãy thực hiện tất cả các bản vẽ ngay trước khi quay lại mã máy khách yêu cầu rút thăm.
Slipp D. Thompson

1
Tôi nhận xét dưới đây về câu trả lời của bạn với nhiều chi tiết hơn. Cố gắng buộc hệ thống vẽ hết trật tự bằng cách gọi các phương thức mà tài liệu nói rõ ràng là không gọi là không mạnh mẽ. Tốt nhất là hành vi không xác định. Nhiều khả năng nó sẽ làm giảm hiệu suất và chất lượng bản vẽ. Tồi tệ nhất nó có thể bế tắc hoặc sụp đổ.
Rob Napier

1
Nếu bạn cần vẽ trước khi quay lại, hãy vẽ vào bối cảnh hình ảnh (hoạt động giống như khung vẽ mà nhiều người mong đợi). Nhưng đừng cố lừa hệ thống tổng hợp. Nó được thiết kế để cung cấp đồ họa chất lượng cao rất nhanh với tối thiểu chi phí tài nguyên. Một phần trong đó là các bước vẽ kết hợp ở cuối vòng lặp chạy.
Rob Napier

1
@RobNapier Tôi không hiểu tại sao bạn trở nên phòng thủ như vậy. Vui lòng kiểm tra lại; không có vi phạm API (mặc dù nó đã được xóa sạch dựa trên đề xuất của bạn. Tôi cho rằng việc gọi phương thức của riêng mình không phải là vi phạm), cũng không có khả năng bế tắc hoặc sụp đổ. Nó cũng không phải là một trò lừa đảo của người Viking; nó sử dụng setNeedDisplay / displayNếu được tạo theo kiểu bình thường. Hơn nữa, tôi đã sử dụng nó khá lâu rồi để vẽ với Quartz theo cách song song với GLKView / OpenGL; nó đã được chứng minh là an toàn, ổn định và nhanh hơn giải pháp bạn đã liệt kê. Nếu bạn không tin tôi, hãy thử nó. Bạn không có gì để mất ở đây.
Slipp D. Thompson

52

Tôi gặp vấn đề với độ trễ lớn giữa việc gọi setNeedDisplay và drawRect: (5 giây). Hóa ra tôi đã gọi setNeedDisplay trong một luồng khác với luồng chính. Sau khi chuyển cuộc gọi này đến luồng chính, độ trễ biến mất.

Hy vọng đây là một số giúp đỡ.


Đây chắc chắn là lỗi của tôi. Tôi có ấn tượng rằng tôi đang chạy trên luồng chính, nhưng mãi đến khi tôi đăng nhập NSThread.isMainThread tôi mới nhận ra có một trường hợp góc mà tôi KHÔNG làm thay đổi lớp trên luồng chính. Cảm ơn vì đã cứu tôi khỏi nhổ tóc!
Ông T

14

Cách chắc chắn, được củng cố, cụ thể, chắc chắn để buộc chế độ xem vẽ đồng bộ (trước khi quay lại mã gọi) là để định cấu hình các CALayertương tác của UIViewlớp với lớp con của bạn .

Trong lớp con UIView của bạn, hãy tạo một displayNow()phương thức cho lớp đó đến khóa học được thiết lập để hiển thị, sau đó chuyển sang làm cho nó trở nên thật :

Nhanh

/// 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()
}

Mục tiêu-C

/// 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];
}

Đồng thời triển khai một draw(_: CALayer, in: CGContext)phương thức sẽ gọi phương thức vẽ riêng tư / nội bộ của bạn (hoạt động vì mọi thứ UIViewlà a CALayerDelegate) :

Nhanh

/// 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()
}

Mục tiêu-C

/// 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();
}

Và tạo internalDraw(_: CGRect)phương thức tùy chỉnh của bạn , cùng với fail-safe draw(_: CGRect):

Nhanh

/// 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)
}

Mục tiêu-C

/// 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];
}

Và bây giờ chỉ cần gọi myView.displayNow()bất cứ khi nào bạn thực sự thực sự cần nó để vẽ (chẳng hạn như từ một CADisplayLinkcuộc gọi lại) . displayNow()Phương pháp của chúng tôi sẽ nói CALayervớidisplayIfNeeded() , mà đồng bộ sẽ gọi trở lại của chúng tôi draw(_:,in:)và làm bản vẽ trong internalDraw(_:), cập nhật trực quan với những gì đang bị lôi kéo vào bối cảnh trước khi chuyển.


Cách tiếp cận này tương tự như @ RobNapier ở trên, nhưng có lợi thế là gọi displayIfNeeded() thêm vào setNeedsDisplay(), điều này làm cho nó đồng bộ.

Điều này là có thể bởi vì CALayer s phơi bày nhiều chức năng vẽ hơn so với UIViewcác lớp do lớp ở mức độ thấp hơn so với chế độ xem và được thiết kế rõ ràng cho mục đích vẽ có thể định cấu hình cao trong bố cục và (giống như nhiều thứ trong Ca cao) được thiết kế để được sử dụng linh hoạt ( như một lớp cha, hoặc như một đại biểu, hoặc là cầu nối đến các hệ thống vẽ khác, hoặc chỉ một mình). Việc sử dụng đúng CALayerDelegategiao thức làm cho tất cả điều này có thể.

Thông tin thêm về khả năng cấu hình của CALayers có thể được tìm thấy trong phần Thiết lập đối tượng lớp của Hướng dẫn lập trình hoạt hình lõi .


Xin lưu ý rằng tài liệu để drawRect:nói rõ ràng "Bạn không bao giờ nên gọi phương thức này trực tiếp cho mình." Ngoài ra, CALay displaynói rõ ràng "Đừng gọi phương thức này trực tiếp." Nếu bạn muốn vẽ đồng bộ contentstrực tiếp trên các lớp , không cần phải vi phạm các quy tắc này. Bạn chỉ có thể vẽ lên lớp contentsbất cứ lúc nào bạn muốn (ngay cả trên các chủ đề nền). Chỉ cần thêm một lớp con để xem cho điều đó. Nhưng điều đó khác với việc đưa nó lên màn hình, cần phải đợi đến thời gian tổng hợp chính xác.
Rob Napier

Tôi nói để thêm một lớp con vì các tài liệu cũng cảnh báo về việc gây rối trực tiếp với lớp của UIView contents("Nếu đối tượng lớp được gắn với một đối tượng xem, bạn nên tránh đặt trực tiếp nội dung của thuộc tính này. trong quan điểm thay thế nội dung của tài sản này trong lần cập nhật tiếp theo. ") Tôi không đặc biệt khuyến nghị phương pháp này; vẽ sớm làm suy yếu hiệu suất và chất lượng bản vẽ. Nhưng nếu bạn cần nó vì một số lý do, thì contentslàm thế nào để có được nó.
Rob Napier

@RobNapier Điểm thực hiện với cuộc gọi drawRect:trực tiếp. Không cần thiết phải chứng minh kỹ thuật này và đã được sửa.
Slipp D. Thompson

@RobNapier Đối với contentskỹ thuật bạn đang đề xuất âm thanh hấp dẫn. Tôi đã thử một cái gì đó như vậy ban đầu nhưng không thể làm cho nó hoạt động được và thấy giải pháp trên có ít mã hơn mà không có lý do gì để không hoạt động như vậy. Tuy nhiên, nếu bạn có một giải pháp hiệu quả cho contentscách tiếp cận, tôi sẽ thích đọc nó (không có lý do gì bạn không thể có hai câu trả lời cho câu hỏi này, phải không?)
Slipp D. Thompson

@RobNapier Lưu ý: Điều này không liên quan gì đến CALayer's display. Đây chỉ là một phương thức công khai tùy chỉnh trong lớp con UIView, giống như những gì được thực hiện trong GLKView (một lớp con UIView khác và là lớp duy nhất do Apple viết mà tôi biết có chức năng DRAW NOW! ).
Slipp D. Thompson

5

Tôi có cùng một vấn đề và tất cả các giải pháp từ SO hoặc Google đều không hiệu quả với tôi. Thông thường, setNeedsDisplaykhông hoạt động, nhưng khi nó không ...
Tôi đã cố gắng gọi setNeedsDisplaychế độ xem theo mọi cách có thể từ mọi chủ đề và nội dung có thể - vẫn không thành công. Chúng tôi biết, như Rob đã nói, rằng

"điều này cần phải được rút ra trong chu kỳ vẽ tiếp theo."

Nhưng vì lý do nào đó nó sẽ không rút ra lần này. Và giải pháp duy nhất tôi tìm thấy là gọi nó bằng tay sau một thời gian, để cho bất cứ thứ gì ngăn chặn rút ra, như thế này:

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];
});

Đó là một giải pháp tốt nếu bạn không cần quan điểm để vẽ lại thực sự thường xuyên. Mặt khác, nếu bạn đang thực hiện một số công cụ chuyển động (hành động), thường không có vấn đề gì với việc chỉ gọisetNeedsDisplay .

Tôi hy vọng nó sẽ giúp được ai đó bị mất ở đó, giống như tôi.


0

Tôi biết điều này có thể là một thay đổi lớn hoặc thậm chí không phù hợp với dự án của bạn, nhưng bạn có cân nhắc việc không thực hiện việc đẩy cho đến khi bạn đã có dữ liệu ? Bằng cách đó, bạn chỉ cần vẽ chế độ xem một lần và trải nghiệm người dùng cũng sẽ tốt hơn - việc đẩy sẽ di chuyển trong đã được tải.

Cách bạn làm điều này là trong UITableView didSelectRowAtIndexPathbạn yêu cầu dữ liệu không đồng bộ. Khi bạn nhận được phản hồi, bạn thực hiện thủ công segue và chuyển dữ liệu tới viewContoder của bạn prepareForSegue. Trong khi đó, bạn có thể muốn hiển thị một số chỉ báo hoạt động, để kiểm tra chỉ báo tải đơn giản https://github.com/jdg/MBProTHERHUD

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.