Frank(wchuang)

I'm Frank from Taiwan, an Sr. iOS Engineer @ByteDance in Shanghai.

[UIView viewWithTag:]

10 Nov 2014 » uiview, objective-c, ios

這篇關於使用 [UIView viewWithTag:] 的文章,雖然已是兩年前的,但在下認為文章內所提及的觀念與用法還是很值得與大家分享。

UIView 有個 tag 屬性以及相對應的方法 -viewWithTag:,讓我們可以輕易的存取特定的 view,而不需要額外的 reference。

這邊作者分享了幾個使用上常見的問題:

  • 不要使用 tags 來儲存資料,例如:陣列存取物件的索引 (index)…,因為這是一件容易造成後續維護者難以理解你的 code 的一件事…底下是一個使用 button 來呈現圖片縮圖的例子,為了記得哪個按鈕被按到以及要呈現哪個對應於 array 中的圖片索引,所以利用 tag 來記錄。(但其實還滿容易這麼幹的啊…在下也常這麼做,要好好檢討了orz)

      - (void)configureThumbnailButton:(UIButton *)thumbnailButton 
                              forPhoto:(NSUInteger)photoIndex {
      		// ...
      	thumbnailButton.tag = 1 + photoIndex;
      		// ...
      }
    
      - (void)thumbnailButtonTapped:(id)sender {
      		UIButton *thumbnailButton = sender;
      		NSUInteger photoIndex = thumbnailButton.tag - 1;
      		id selectedPhoto = [self.photoArray objectAtIndex:photoIndex];
      		[self showPhoto:selectedPhoto];
      }
    
  • 存取 view 額外資料的幾個比較正確的方式:

    我們來探討一下為什麼會寫出上述例子的程式碼,其實也只是想要知道 button 對應的 index,或是 button 真正對應的圖片縮圖,此時更適合的方式應該是另外做一個 custom subClass 來儲存這些有用的額外資料,延續上述例子,應該可以這麼做:

      @interface PhotoThumbnailButton : UIButton
    
      @property (nonatomic, assign) NSInteger photoIndex;
      // OR
      @property (nonatomic, strong) Photo *photo;
    
      @end
    

    這時候你會說,如果每次都要這樣也太麻煩了吧!的確,每次都要建個 subClass 有時可能不符合實際情況,底下也提供一個用法:Associated references。

  • Associated references:提供了 objc_getAssociatedObject 以及 objc_setAssociatedObject 等方法,可以利用特定的 key 及 policy 存取特定的 obejct。如:

      #import <objc/runtime.h>
    
      static char kThumbnailButtonAssociatedPhotoKey;
    
      // ...
    
      - (void)setAssociatedPhoto:(Photo *)associatedPhoto
              forThumbnailButton:(UIButton *)thumbnailButton {
      		objc_setAssociatedObject(thumbnailButton,
      		                         &kThumbnailButtonAssociatedPhotoKey,
                                   associatedPhoto,
                                   OBJC_ASSOCIATION_RETAIN_NONATOMIC);
      }
    
      - (Photo *)associatedPhotoForThumbnailButton:(UIButton *)thumbnailButton {
          return objc_getAssociatedObject(thumbnailButton,
                                  		 &kThumbnailButtonAssociatedPhotoKey);
      }
    

    存取時,

    	- (void)configureThumbnailButtonForPhoto:(Photo *)photo {
      		// ...
      		[self setAssociatedPhoto:photo forThumbnailButton:thumbnailButton];
      		// ...
      }
    
      - (void)thumbnailButtonTapped {
      		Photo *photo = [self associatedPhotoForThumbnailButton:thumbnailButton];
      		// ...
      }
    

    真的是很酷啊… 可是,如果只是想要快速直接的知道是哪一個 view,這樣的方式還是不夠直覺…所以,有些情況下,你還是會想直接利用 UIView’s tag 存取,但重點是請遵循 UIView’s tag 是 UIView 的唯一識別、可讀性以及易維護性的觀念,而使用 #define 或 enum value 來設定 tags,在你之後的開發者會很感謝你的..XD,例如:

      enum MyViewTags {
          kTitleLabelTag = 1,
      		kSendButtonTag,
      		kSomeOtherViewTag
      };
    
      // ...
    
      if (sender.tag == kSendButtonTag) {
      		// ...
      }
    
  • 結論:
    • 不要使用 tags 來儲存資料 -> use sub-class or associated references resolving this.
    • 多利用 public properties of sub-class for getting references of subviews.
    • If you do use tags, do not use magic numbers. Use named constants.
  • Ref.