contentView.subViews

本文详细解析了UITableView在iOS应用开发中的重用机制问题。针对自定义cellView时出现的界面元素重叠现象,提供了有效的解决方案,并展示了如何正确地分离界面加载与数据加载过程。

最近项目中要用到UITableView,在网上搜了一遍看大家的用法都差不多,我是通过[cell.contentView addSubview:cellView]来添加我自定义的cellView,但我发现了一个问题,在UITableView滚动过一屏后选中后面的一项时会发现和前面的竟然重叠到一起了,后面通过打印[cell.contentView.subviews count]发现随着滚动条的滚动count不断增大。最后通过以下语句解决问题:

             for(UIView *view in cell.contentView.subViews)

               {

                   if([view isKindOfClass:[UIView class]])

                    {

                         [view removeFromSuperview];

                     }

                 }

问题虽然解决了但在和同事的讨论时发现这个问题可能是用法不当造成的。网上找到的代码一般都是

    static NSString *tag = @"cell";

         if(cell == nil)

       {

           cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:tag]autorelease];

        }

  ……

然后在后面向cell添加数据,网上都是向cell中自带的textLabel添加数据所以不会出现重叠的现象,而我则是向自定义的cellView添加数据,这样只要有滚动它就向里面添加cellView,所以cellView越加越多。后面通过将界面加载与数据加载解决,具体实现如下:

-(UITableViewCell *)tableView:(UITableView *)table cellForRowAtIndexPath:(NSIndexPath *)indexPath

{

     static NSString *tag = @"cell";//由于用了两种界面模式所以两个tag

     static NSString *tag1 = @"cell1";

 

   UITableViewCell * cell;

   CGRect cellFram = CGRectMake(0 , 0, 320 ,100);

    if([indexPath row] == [cells count] )//numberOfRowsInSection中我返回的是[cells count]+1,最后有一个“加载更多。。。”项

   {

       cell = [tableView dequeueReusableCellWithIdentifier: tag1];

      if( cell == nil )

       {

         cell = [[[UITableViewCell alloc]initWithStyle: UITableViewCellStyleDefault reuseIdentifier: tag1] autorelease];

         cell.frame = cellFrame;

         cell.backgroundColor = [UIColor clearColor];

        //界面加载

          cell.textLabel.frame = cellFrame;

          cell.textLabel.textAlignment = UITextAlignmentCenter;

           cell.textLabel.backgroundColor = [UIColor clearColor];

           cell.textLabel.tag = 50;

 

           UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc]initWithFrame:CGRectMake(75,40,24,24)];

           [activityIndicator setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleGray];

            activityIndicator.tag = 51;

            [cell.contentView addSubview: activityIndicator];

            [activityIndicator release];

        }

//数据加载

   if([indexPath row] == [items count])  //最后一条

     {

           UILabel *more = (UILabel *)[cell.contentView viewWithTag: 50];

            more.text = @"已经是最后一条";

           

            UIActivityIndicatorView *activity = (UIActivityIndicatorView *)[cell.contentView viewWithTag:51];

            [activity stopAnimating];

      }

    else

     {

           UILabel *more = (UILabel *)[cell.contentView viewWithTag: 50];

            more.text = @"正在加载更多……";

           

            UIActivityIndicatorView *activity = (UIActivityIndicatorView *)[cell.contentView viewWithTag:51];

            [activity startAnimating];

     }

    }

else

{

      cell = [tableView dequeueReusableCellWithIdentifier: tag];

        if(cell == nil)

          { 

             cell = [[[UITableViewCell alloc]initWithStyle: UITableViewCellStyleDefault reuseIdentifier: tag] autorelease];

             cell.frame = cellFrame;

             cell.backgroundColor = [UIColor clearColor];

              //界面加载

               RWXZCellView *cellView = [[RWXZCellView alloc] initWithFrame:cellFrame];

              cellView.backgroundColor = [UIColor clearColor];

                cellView.tag = 100;

               [cell.contentView addSubview:cellView];

                [cellView release];

            }

                //加载数据

               RWXZCellView *cellView =(RWXZCellView *)[cell.contentView viewWithTag:100];

              [cellView setItemInfo:[cells objectAtIndex:[indexPath row]]];

   }

 

return cell;

}

 这样就不会出现重叠啦!

    

import Cocoa class MessageDemoController: NSViewController { private var messageListView = TPGuardMessageListView() private var toggleButton = NSButton() // 新增第二个消息列表 private var messageListView2 = TPGuardMessageListView() private var toggleButton2 = NSButton() // 第二个控制按钮 private var contentView = NSView() // 使用真实的 TPGuardDeviceShareMessage private var demoMessages: [TPGuardDeviceShareMessage] = { var messages = [TPGuardDeviceShareMessage]() for i in 1...5 { let message = TPGuardDeviceShareMessage() message.messageID = "msg_\(i)" message.messageType = (i % 2 == 0) ? .deviceShareIPC : .deviceShareNVR // 交替类型 message.content = "User \(i) wants to share a device with you." message.sharerUserName = "user\(i)" message.deviceModel = "Model-\(i)" message.deviceName = "Device-\(i)" message.time = Date().timeIntervalSince1970 - TimeInterval(i * 3600) // 间隔1小时 message.expiry = message.time + 86400 // 24小时后过期 messages.append(message) } return messages }() private var demoMessages2: [TPGuardDeviceShareMessage] = { var messages = [TPGuardDeviceShareMessage]() for i in 1...3 { let message = TPGuardDeviceShareMessage() message.messageID = "msg2_\(i)" message.messageType = (i % 2 == 0) ? .deviceShareIPC : .deviceShareNVR // 交替类型 message.content = "User \(i) wants to share a device with you." message.sharerUserName = "user\(i)" message.deviceModel = "Model-\(i)" message.deviceName = "Device-\(i)" message.time = Date().timeIntervalSince1970 - TimeInterval(i * 3600) // 间隔1小时 message.expiry = message.time + 86400 // 24小时后过期 messages.append(message) } return messages }() override func loadView() { view = NSView(frame: NSRect(x: 0, y: 0, width: 400, height: 2000)) view.wantsLayer = true view.layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor } override func viewDidLoad() { super.viewDidLoad() setupUI() setupConstraints() loadDemoData() } private func setupUI() { // 消息列表的滚动容器 let scrollView1 = NSScrollView() scrollView1.translatesAutoresizingMaskIntoConstraints = false scrollView1.documentView = messageListView scrollView1.hasVerticalScroller = true scrollView1.borderType = .noBorder let scrollView2 = NSScrollView() scrollView2.translatesAutoresizingMaskIntoConstraints = false scrollView2.documentView = messageListView2 scrollView2.hasVerticalScroller = true scrollView2.borderType = .noBorder // 消息列表视图 messageListView.translatesAutoresizingMaskIntoConstraints = false messageListView.messageListViewStatusDidChangeCallback = { [weak self] height, isCollapsed in self?.view.layoutSubtreeIfNeeded() } // 第二个消息列表视图(新增) messageListView2.translatesAutoresizingMaskIntoConstraints = false messageListView2.messageListViewStatusDidChangeCallback = { [weak self] height, isCollapsed in self?.view.layoutSubtreeIfNeeded() } view.addSubview(contentView) contentView.addSubview(scrollView1) contentView.addSubview(scrollView2) // 切换折叠状态的按钮 toggleButton.title = "Toggle Collapse" toggleButton.bezelStyle = .rounded toggleButton.target = self toggleButton.action = #selector(toggleList2State) toggleButton.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(toggleButton) // 第二个切换按钮(新增) toggleButton2.title = "Toggle List 2" toggleButton2.bezelStyle = .rounded toggleButton2.target = self toggleButton2.action = #selector(toggleListState) toggleButton2.translatesAutoresizingMaskIntoConstraints = false contentView.addSubview(toggleButton2) } private func setupConstraints() { contentView.snp.makeConstraints { make in make.edges.equalTo(view) make.width.equalTo(420) //make.height.equalTo(700) } // 第一个滚动容器(列表1) let scrollView1 = contentView.subviews[0] as! NSScrollView scrollView1.snp.makeConstraints { make in make.top.equalTo(contentView).offset(20) make.leading.trailing.equalTo(contentView) make.height.equalTo(300) // 固定高度,允许内部滚动 } // 第二个滚动容器(列表2) let scrollView2 = contentView.subviews[1] as! NSScrollView scrollView2.snp.makeConstraints { make in make.top.equalTo(scrollView1.snp.bottom).offset(10) make.leading.trailing.equalTo(contentView) make.height.equalTo(300) // 固定高度,允许内部滚动 } // // 第一个列表的约束 // messageListView.snp.makeConstraints { make in // make.top.equalTo(contentView).offset(20) // make.leading.trailing.equalTo(contentView) // } // // // 第二个列表的约束(紧接第一个列表下方) // messageListView2.snp.makeConstraints { make in // make.top.equalTo(messageListView.snp.bottom).offset(30) // 间隔30pt // make.leading.trailing.equalTo(contentView) // } // 第一个按钮的约束 toggleButton.snp.makeConstraints { make in make.top.equalTo(scrollView2.snp.bottom).offset(20) make.centerX.equalTo(contentView).multipliedBy(0.5) // 左侧半宽位置 } // 第二个按钮的约束 toggleButton2.snp.makeConstraints { make in make.top.equalTo(toggleButton) make.centerX.equalTo(contentView).multipliedBy(1.5) // 右侧半宽位置 make.bottom.equalTo(contentView).offset(-20) } } private func loadDemoData() { messageListView.deviceShareMessages = demoMessages messageListView.reloadMessages() messageListView2.deviceShareMessages = demoMessages2 messageListView2.reloadMessages() } @objc private func toggleListState() { messageListView.toggleCollapseState() } @objc private func toggleList2State() { messageListView2.toggleCollapseState() } } 这样改造后,看到了scrollview,但是在滚动容器内根本看不到卡片列表了,为什么
09-25
我现在想实现这样的:首先在所有的水平阶段(竖屏)保持现在的所有逻辑不变,然后在垂直(横屏)状态下,修改原来的布局逻辑,将除了按钮为item.type为case .LiveMultiPlay, .LiveQuality, .LivePlay, .LiveVolume, .LiveLandscape这五个按钮之外的需要用到的按钮排成双列显示(跟填充背景色的按钮一样,只有填充背景色按钮的才在横屏时排双列显示),请帮我修改整个代码,一次成功。 import SnapKit final class TPMediaPlayerTooView : UIView, TPMediaViewRefreshDelegate { private(set) var clickAction : TPMediaPlayerClickAction?; private var viewBtns = [UIButton](); private var btnItems = [TPMediaPlayToolItem](); private let maxCnt : Int = 5; private lazy var contentView : UIScrollView = { let view = UIScrollView(); view.showsHorizontalScrollIndicator = false; view.showsVerticalScrollIndicator = false; view.backgroundColor = UIColor.clear; view.delegate = self; return view }() var isVertical : Bool = false { didSet { self.relaySubviews(views: self.viewBtns, isVertical: isVertical); } } var isInScreen: Bool = false { didSet { if (!isInScreen && !self.indicator.isHidden) { self.contentView.contentOffset = .zero; self.indicator.offset = 0; } } } var darkStyle : Bool = false; var enableIndicator : Bool = false; let indicatorWidth : CGFloat = 25; private(set) lazy var indicator : TPProgressIndicatorView = { let view = TPProgressIndicatorView(); view.clipsToBounds = true; view.backgroundColor = UIColor.tpbGrey//.tpbProgressBackground; view.isHidden = true; return view; }() init(items:[TPMediaPlayToolItem], downloadCenterViewModel: TPDownloadCenterViewModel? = nil, clickAction:@escaping TPMediaPlayerClickAction) { super.init(frame: CGRectZero); self.addSubview(self.contentView); self.contentView.snp.remakeConstraints { make in make.top.bottom.leading.trailing.equalToSuperview() }; self.clickAction = clickAction; self.downloadCenterViewModel = downloadCenterViewModel; self.downloadCenterViewModel?.reloadDownloadTaskNumberViewInToolViewAction = { [weak self](downloadTaskNumber: Int, failedTaskNumber: Int) in if let self = self { for (index, item) in items.enumerated() { if index >= 0 && index < self.viewBtns.count && item.type == .PlayBackDownload { reloadDownloadNumberSubScript(downloadTaskNumber: downloadTaskNumber, errorTaskNumber: failedTaskNumber); } } } } self.setItems(items: items); } var downloadCenterViewModel: TPDownloadCenterViewModel?; lazy var downloadingTaskNumberLabel: UILabel = { let downloadingTaskNumberLabel = UILabel(frame: .zero); downloadingTaskNumberLabel.textColor = UIColor.tpbTextWhite; downloadingTaskNumberLabel.font = .projectFont(ofSize: 12); downloadingTaskNumberLabel.textAlignment = .center; downloadingTaskNumberLabel.backgroundColor = .tpbRed; return downloadingTaskNumberLabel; }() lazy var downloadErrorImageView: UIImageView = { let imageView = UIImageView(frame: .zero) imageView.backgroundColor = .clear; imageView.image = UIImage(named: "playback_download_error"); return imageView; }() public func setItems(items:[TPMediaPlayToolItem]) { if self.btnItems.elementsEqual(items, by: { item1, item2 in return item1.type == item2.type }) { refresh(self.darkStyle); return } self.viewBtns.forEach({$0.removeFromSuperview()}); self.viewBtns.removeAll() self.btnItems.removeAll(); self.btnItems.append(contentsOf: items); for item in items { let btn = UIButton.init(type: .custom); btn.setImage(TPImageLiteral(item.normalImgUri), for: .normal); btn.imgUri = item.normalImgUri; btn.secondUri = item.secondUri; var img = TPImageLiteral(item.highImgUri) btn.setImage(img, for: .highlighted) img = TPImageLiteral(item.disableImgUri) btn.setImage(img, for: .disabled) btn.uriList = item.uriList; btn.tag = TPMediaPreviewUtils.kMediaPlayerSubviewBaseTag + item.type.rawValue; btn.addTarget(self, action: #selector(clickViewAction), for: .touchUpInside); // 如果有图片变化的需要在变化的时候再次修改 btn.accessibilityLabel = item.accessibilityLabel // 根据type判断是否添加背景圆角样式 switch item.type { case .LiveMultiPlay, .LiveQuality, .LivePlay, .LiveVolume, .LiveLandscape: // 屏幕上的五个按钮不添加 break default: // 其他类型添加背景圆角 btn.backgroundColor = UIColor.tpbBackgroundForPopup btn.layer.cornerRadius = 12 btn.clipsToBounds = true btn.imageEdgeInsets = UIEdgeInsets(top: 6, left: 0, bottom: 6, right: 0) } self.contentView.addSubview(btn); self.viewBtns.append(btn); } relaySubviews(views: self.viewBtns, isVertical: isVertical) refresh(self.darkStyle); reloadDownloadNumberSubScript(downloadTaskNumber: self.downloadCenterViewModel?.downloadingTaskNumber ?? 0, errorTaskNumber: self.downloadCenterViewModel?.failedTaskNumber ?? 0) } func reloadDownloadNumberSubScript(downloadTaskNumber: Int, errorTaskNumber: Int){ relayoutDownloadingSubviews() if downloadTaskNumber > errorTaskNumber { // 存在正在下载的任务,优先显示正在下载的任务数 downloadingTaskNumberLabel.isHidden = false; downloadErrorImageView.isHidden = true; downloadingTaskNumberLabel.text = String(format: "%d", downloadTaskNumber - errorTaskNumber); } else if downloadTaskNumber == errorTaskNumber && errorTaskNumber > 0 { // 没有正在下载任务,但是有下载失败任务,显示error图标 downloadErrorImageView.isHidden = false; downloadingTaskNumberLabel.isHidden = true; } else { downloadingTaskNumberLabel.isHidden = true; downloadErrorImageView.isHidden = true; } } private func relayoutDownloadingSubviews() { guard let downBtn = self.viewWithType(type: .PlayBackDownload) else { return } let blk = { (view:UIView, clip:Bool) in if (view.superview != downBtn) { view.removeFromSuperview() downBtn.addSubview(view) let downloadingTaskNumberLabelHW = 16.0; view.snp.remakeConstraints { make in make.leading.equalToSuperview().offset(30); make.top.equalToSuperview().offset(-6); make.width.height.equalTo(downloadingTaskNumberLabelHW); } if (clip) { view.layer.cornerRadius = downloadingTaskNumberLabelHW / 2.0; view.clipsToBounds = true; } } } blk(self.downloadErrorImageView, false); blk(self.downloadingTaskNumberLabel, true); } public func viewWithType(type:TPMediaPlayerFuncType) -> UIButton? { let tag = type.rawValue + TPMediaPreviewUtils.kMediaPlayerSubviewBaseTag; return self.viewWithTag(tag) as? UIButton; } public func view(types:[TPMediaPlayerFuncType]) -> [UIButton]? { var items : [UIButton] = [UIButton](); for type in types { guard let btn = self.viewWithType(type: type) else { continue } items.append(btn); } return items; } public func setEnable(enable:Bool) { if enable { self.viewBtns.forEach({$0.isEnabled = true}) } else { self.viewBtns.forEach({$0.isEnabled = false}) } } public func setEnable(type:TPMediaPlayerFuncType, enable:Bool) { self.viewWithType(type: type)?.isEnabled = enable; } public func hitInBtn(point:CGPoint, parent:UIView) -> Bool { if (self.isHidden) { return false; } for btn in self.viewBtns { let btnFrame = btn.convert(btn.bounds, to: parent); if (btnFrame.contains(point)) { self.clickViewAction(btn); return true } } return false; } public func itemTypes() -> [TPMediaPlayerFuncType] { var types : [TPMediaPlayerFuncType] = []; self.btnItems.forEach({ types.append($0.type) }); return types; } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() relaySubviews(views: self.viewBtns, isVertical: isVertical) } override func didMoveToSuperview() { super.didMoveToSuperview() if !self.isInScreen, self.indicator.superview == nil, let superView = self.superview { superView.addSubview(self.indicator) self.indicator.snp.remakeConstraints { make in make.centerX.equalTo(self); make.size.equalTo(CGSize(width: indicatorWidth, height: 2)); make.top.equalTo(self.snp.bottom).offset(5); } self.indicator.layer.cornerRadius = 1; } } override func removeFromSuperview() { super.removeFromSuperview() self.indicator.removeFromSuperview() } //MARK: - TPMediaViewRefreshDelegate func refresh(_ darkStyle: Bool) { self.darkStyle = darkStyle; self.viewBtns.forEach({ $0.refresh(darkStyle)}) self.indicator.isHidden = self.isHidden || !enableIndicator || darkStyle || self.viewBtns.count <= maxCnt; if (!self.indicator.isHidden) { self.handleScroll(self.contentView); } } //MARK: - Private private func relaySubviews(views:[UIButton], isVertical:Bool) { if (views.count <= 0) { return } var lastBtn : UIButton? = nil var lrMargin : CGFloat = 0 let btnWH : CGFloat = 130.0 let btnH : CGFloat = 60.0 var itemMargin : CGFloat = 15.0 var vContH : CGFloat = 0 var minVMargin : CGFloat = isVertical && views.count > 3 ? 10 : 24 // 竖屏状态下 if (!isVertical) { // 恢复竖屏下非Live类型按钮的默认背景色 for subView in contentView.subviews { if let btn = subView as? UIButton, !btn.isLiveButtonType() { btn.backgroundColor = UIColor.tpbBackgroundForPopup } } if isInScreen { let itemCnt = views.count// > 5 ? 5 : views.count lrMargin = views.count <= 2 ? (CGRectGetWidth(self.frame) - btnWH * CGFloat(itemCnt)) / CGFloat(itemCnt + 1) : lrMargin let totalMargin = CGRectGetWidth(self.frame) - 2.0 * lrMargin - btnWH * CGFloat(itemCnt) itemMargin = totalMargin / CGFloat(max(1, (itemCnt - 1))) } else { if (CGFloat((views.count - 1) * 24) + CGFloat(views.count) * btnWH) > CGRectGetWidth(UIScreen.main.bounds) || views.count > 5 { let itemCnt : CGFloat = views.count > maxCnt ? (CGFloat(maxCnt) + 0.5) : CGFloat(views.count) lrMargin = views.count <= 2 ? (CGRectGetWidth(self.frame) - btnWH * CGFloat(itemCnt)) / CGFloat(itemCnt + 1) : lrMargin + 15 let totalMargin = CGRectGetWidth(self.frame) - 2.0 * lrMargin - btnWH * CGFloat(itemCnt) itemMargin = abs(totalMargin / CGFloat(max(1, itemCnt - (CGFloat(views.count) > itemCnt ? 0.5 : 1)))) } else { lrMargin = (CGRectGetWidth(self.frame) - (CGFloat((views.count - 1) * 24) + CGFloat(views.count) * btnWH)) / 2 } } } else { // 横屏特殊处理 for subView in contentView.subviews { if let btn = subView as? UIButton, !btn.isLiveButtonType() { btn.backgroundColor = UIColor.tpbCardDark } } minVMargin = isVertical && views.count > 3 ? 0 : 24; let itemCnt = CGFloat(views.count); let vMargin : CGFloat = 32; let contH = max(0, itemCnt - 1) * itemMargin + (btnH + vMargin) * itemCnt; if contH > CGRectGetHeight(self.frame) { let totalMargin = CGRectGetHeight(self.frame) - 2.0 * lrMargin - btnH * CGFloat(itemCnt) itemMargin = totalMargin / CGFloat(max(1, (itemCnt - 1))) vContH = max(0, itemCnt - 1) * itemMargin + btnH * itemCnt lrMargin = max(minVMargin, (CGRectGetHeight(self.frame) - vContH) / 2) } else { itemMargin = itemMargin + vMargin vContH = max(0, itemCnt - 1) * itemMargin + (btnH + vMargin) * itemCnt; lrMargin = max(minVMargin, (CGRectGetHeight(self.frame) - vContH) / 2) } } for i in 0..<views.count { let btn = views[i]; guard let lastView = lastBtn else { btn.snp.remakeConstraints { make in if (isVertical) { make.top.equalToSuperview().offset(lrMargin / 2); make.centerX.equalToSuperview(); } else { make.leading.equalToSuperview().offset(lrMargin); make.centerY.equalToSuperview(); } make.width.equalTo(btnWH); make.height.lessThanOrEqualTo(btnWH); make.height.greaterThanOrEqualTo(btnH) }; lastBtn = btn; continue } btn.snp.remakeConstraints { make in make.width.height.equalTo(lastView); if (isVertical) { make.centerX.equalTo(lastView); make.top.equalTo(lastView.snp_bottomMargin).offset(itemMargin); if (i == views.count - 1) { make.bottom.equalToSuperview().offset(0 - itemMargin); } } else { make.centerY.equalTo(lastView); make.leading.equalTo(lastView.snp.trailing).offset(itemMargin); if (i == views.count - 1) { if (isVertical) { make.bottom.equalToSuperview(); } else { make.trailing.equalToSuperview().offset(0 - lrMargin); } } } } lastBtn = btn; } let itemCnt : CGFloat = CGFloat(self.viewBtns.count); if (isVertical) { self.contentView.contentSize = CGSizeMake(CGRectGetWidth(self.frame), max(CGRectGetHeight(self.frame) - 2 * minVMargin, vContH)) } else { let gap = enableIndicator && self.viewBtns.count > self.maxCnt ? 5.0 : 0.0; self.contentView.contentSize = CGSize(width: max(CGRectGetWidth(self.frame), lrMargin * 2 + max(0, itemCnt - 1) * itemMargin + itemCnt * btnWH) + gap, height: CGRectGetHeight(self.frame)); } } @objc private func clickViewAction(_ sender:UIButton) { guard let type = TPMediaPlayerFuncType(rawValue: sender.tag - TPMediaPreviewUtils.kMediaPlayerSubviewBaseTag) else { return } for item in self.btnItems { if (type == item.type) { guard let callback = item.clickAction else { self.clickAction?(type, false, nil); break } callback(type, false, nil); break; } } } } extension UIButton { func isLiveButtonType() -> Bool { let tag = self.tag - TPMediaPreviewUtils.kMediaPlayerSubviewBaseTag guard let type = TPMediaPlayerFuncType(rawValue: tag) else { return false } switch type { case .LiveMultiPlay, .LiveQuality, .LivePlay, .LiveVolume, .LiveLandscape: return true default: return false } } @objc var textLable : UILabel { get { if let view = objc_getAssociatedObject(self, unsafeBitCast(#selector(setter: self.textLable), to: UnsafeRawPointer.self)) as? UILabel { return view } let view = UILabel.init() view.font = .projectFont(ofSize: 12) view.textColor = UIColor.tpbTextWhite view.textAlignment = .center view.backgroundColor = .clear self.addSubview(view) view.snp.remakeConstraints { make in make.center.equalTo(self) } self.textLable = view return view } set { objc_setAssociatedObject(self, unsafeBitCast(#selector(setter: textLable), to: UnsafeRawPointer.self), newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } @objc var subTextLabel: UILabel { get { if let view = objc_getAssociatedObject(self, unsafeBitCast(#selector(setter: self.subTextLabel), to: UnsafeRawPointer.self)) as? UILabel { return view } let view = UILabel.init() view.font = .projectFont(ofSize: 12) view.textColor = .tpbRed view.textAlignment = .center view.backgroundColor = .clear self.addSubview(view) view.snp.remakeConstraints { make in make.centerX.equalTo(self) make.top.equalTo(self.snp.bottom) make.height.equalTo(15) } self.subTextLabel = view return view } set { objc_setAssociatedObject(self, unsafeBitCast(#selector(setter: subTextLabel), to: UnsafeRawPointer.self), newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } } extension TPMediaPlayerTooView : UIScrollViewDelegate { func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { handleScroll(scrollView) } func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { handleScroll(scrollView, decelerate: decelerate) } func scrollViewDidScroll(_ scrollView: UIScrollView) { handleScroll(scrollView) } private func handleScroll(_ scrollView:UIScrollView, decelerate: Bool = false) { if (decelerate || self.indicator.isHidden) { return } let len = scrollView.contentSize.width - CGRectGetWidth(self.frame); let offset = scrollView.contentOffset.x; if (len >= 1) { self.indicator.offset = offset / len * (indicatorWidth - self.indicator.width); } } } final class TPProgressIndicatorView : UIView { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } var width : CGFloat = 11 { didSet { width != oldValue ? self.setNeedsDisplay() : nil; } } var color : UIColor = .tpbGreen { didSet { color != oldValue ? self.setNeedsDisplay() : nil } } var offset : CGFloat = 0 { didSet { if (offset != oldValue) { self.setNeedsDisplay() } } } init() { super.init(frame: .zero) } override func draw(_ rect: CGRect) { super.draw(rect) let pathH = CGRectGetHeight(rect); let path = UIBezierPath(rect: CGRect(x: offset, y: 0, width: width , height: pathH)) path.lineWidth = pathH; color.setStroke() path.stroke() } }
10-16
帮我分析下面代码:// // TPGuardEventTypeFilterView.swift // OmadaSurveillance // // Created by LSL on 5/9/25. // import Cocoa import TPBMDesignKit var filterEventTypeDictionary: [Int: String] = [ 2:"Motion Detection", 3:"Camera Tampering", 4:"Line Crossing Detection", 5:"Intrusion Detection", 6:"Region Entering Detection", 7:"Region Exiting Detection", 8:"Loitering Detection", 14:"Audio Exception Detection", 16:"Scene Changing Detection", 19:"Video Loss Detection", 21:"Human Detection", 22:"Vehicle Detection", 23:"Object Abandoned Detection", 101:"Sdcard Missing", 102:"Sdcard Full", 103:"HDD Missing", 104:"HDD Full", 105:"Device Offline", 106:"Sdcard Exception", 107:"HDD Exception" ] class TPGuardEventTypeFilterView: NSView { var availableTypesList: [PlaybackEventType] = [] { didSet { eventTypeSelectionController.availableTypesList = availableTypesList } } var popover: TPBPopover? var selectBox = SwitchButton() var contentView = NSView() var dropDownButton = TPBButton() var selectedEventTypeList: [PlaybackEventType] = [] private var eventTypeSelectionController = TPGuardEventTypeSelectionController() override init(frame frameRect: NSRect) { super.init(frame: frameRect) setupSubviews() makeConstraints() bindActions() } required init?(coder: NSCoder) { fatalError() } func setupSubviews() { self.wantsLayer = true self.layer?.backgroundColor = NSColor.tpSecondaryButton.cgColor self.layer?.cornerRadius = 4 self.dropDownButton.iconImage = NSImage(named: "downArrow") self.dropDownButton.fillColor = nil self.dropDownButton.hoverColor = nil popover = TPBPopover(contentViewController: eventTypeSelectionController) popover?.closesWhenOtherWindowClicked = true addSubview(contentView) addSubview(dropDownButton) } func reset() { selectedEventTypeList = availableTypesList eventTypeSelectionController.selectedTypesList = availableTypesList eventTypeSelectionController.availableTypesList = availableTypesList setupContentView() } func makeConstraints() { contentView.snp.makeConstraints { make in make.leading.equalTo(self).offset(10) make.top.bottom.equalTo(self).inset(7) make.height.equalTo(18) } dropDownButton.snp.makeConstraints { make in make.leading.equalTo(contentView.snp.trailing).offset(8) make.trailing.equalTo(self).offset(-10) make.centerY.equalTo(self) make.width.height.equalTo(16) } } func bindActions() { dropDownButton.onClickCallback = { [weak self] in guard let self = self else { return } guard let isVisible = self.popover?.isVisible else { return } if !isVisible { self.popover?.show(in: self, positionStyle: .below, space: 5) } else { self.popover?.dismiss() } } eventTypeSelectionController.eventTypeDidSelectCallback = { [weak self] selectedTypes in guard let self = self else { return } self.selectedEventTypeList = selectedTypes self.setupContentView() } } func setupContentView() { for view in contentView.subviews { view.removeFromSuperview() } if (selectedEventTypeList.count > 1) { let item = TPGuardEventTypeItem(eventType: selectedEventTypeList[0].rawValue) let itemElse = TPGuardEventTypeItem(eventType: 0) item.deleteCallback = { self.removeEventType() } item.label.text = eventTypeSelectionController.titleForEventType(eventType: selectedEventTypeList[0]) item.label.toolTip = eventTypeSelectionController.titleForEventType(eventType: selectedEventTypeList[0]) contentView.addSubview(item) contentView.addSubview(itemElse) var elseToolTip = "" for eventType in selectedEventTypeList { if let index = selectedEventTypeList.firstIndex(of: eventType) { if index > 0 { elseToolTip += eventTypeSelectionController.titleForEventType(eventType: eventType) if index < selectedEventTypeList.count - 1 { elseToolTip += "\n" } } } } itemElse.label.toolTip = elseToolTip item.snp.makeConstraints { make in make.top.bottom.leading.equalTo(contentView) make.width.equalTo(115) } itemElse.snp.makeConstraints { make in make.top.bottom.trailing.equalTo(contentView) make.leading.equalTo(item.snp.trailing).offset(8) } } else if (selectedEventTypeList.count == 1) { let item = TPGuardEventTypeItem(eventType: selectedEventTypeList[0].rawValue) item.label.text = eventTypeSelectionController.titleForEventType(eventType: selectedEventTypeList[0]) item.deleteCallback = { self.removeEventType() } contentView.addSubview(item) item.snp.makeConstraints { make in make.top.bottom.leading.equalTo(contentView) make.width.equalTo(115) } } } func removeEventType() { selectedEventTypeList.removeFirst() setupContentView() } func addEventType(eventType:Int) { if let type = PlaybackEventType(rawValue: eventType) { if selectedEventTypeList.contains(type) { return } selectedEventTypeList.append(type) setupContentView() } } } class TPGuardEventTypeItem: NSView { var label = TPBLabel() var deleteButton = TPBButton() var deleteCallback: (()->())? init(eventType: Int) { super.init(frame: .zero) self.wantsLayer = true self.layer?.backgroundColor = NSColor.tpSecondaryButtonHover.cgColor self.layer?.cornerRadius = 2 label.font = .tpr13Regular() label.textColor = .tpTextPrimary if (eventType > 0) { self.label.text = filterEventTypeDictionary[eventType] ?? "" } else if (eventType == 0) { self.label.text = "..." } deleteButton.iconImage = NSImage(named: "closeButton") deleteButton.fillColor = nil deleteButton.hoverColor = nil deleteButton.onClickCallback = { if let deleteCallback = self.deleteCallback { deleteCallback() } } addSubview(label) if (eventType > 0) { addSubview(deleteButton) } if (eventType > 0) { label.snp.makeConstraints { make in make.top.bottom.equalTo(self) make.leading.equalTo(self).offset(4) } deleteButton.snp.makeConstraints { make in make.leading.equalTo(label.snp.trailing).offset(4) make.trailing.equalTo(self).offset(-4) make.centerY.equalTo(self) make.width.height.equalTo(12) } } else if (eventType == 0) { label.snp.makeConstraints { make in make.top.bottom.equalTo(self) make.leading.trailing.equalTo(self).inset(4) } } } required init?(coder: NSCoder) { fatalError() } }
最新发布
10-22
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值