UITapGestureRecognizer

本文介绍如何使用UIKit框架中的UITapGestureRecognizer来实现iOS应用中的不同触控手势识别,包括单指单击、单指双击、双指单击及双指双击等,并展示了如何设置手势的触发条件以及避免手势冲突。
//单指单击
2: UITapGestureRecognizer *singleFingerOne = [[UITapGestureRecognizer alloc]initWithTarget:self
3: action:@selector(handleSingleFingerEvent:)];
4: singleFingerOne.numberOfTouchesRequired = 1; //手指数
5: singleFingerOne.numberOfTapsRequired = 1; //tap次数
6: singleFingerOne.delegate = self;
7:
8: //单指双击
9: UITapGestureRecognizer *singleFingerTwo = [[UITapGestureRecognizer alloc] initWithTarget:self
10: action:@selector(handleSingleFingerEvent:)];
11: singleFingerTwo.numberOfTouchesRequired = 1;
12: singleFingerTwo.numberOfTapsRequired = 2;
13: singleFingerTwo.delegate = self;
14:
15: //双指单击
16: UITapGestureRecognizer *doubleFingerOne = [[UITapGestureRecognizer alloc] initWithTarget:self
17: action:@selector(handleDoubleFingerEvent:)];
18: doubleFingerOne.numberOfTouchesRequired = 2;
19: doubleFingerOne.numberOfTapsRequired = 1;
20: doubleFingerOne.delegate = self;
21:
22: UITapGestureRecognizer *doubleFingerTwo = [[UITapGestureRecognizer alloc] initWithTarget:self
23:    action:@selector(handleDoubleFingerEvent:)];
24: doubleFingerTwo.numberOfTouchesRequired = 2;
25: doubleFingerTwo.numberOfTapsRequired = 2;
26: doubleFingerTwo.delegate = self;
27:
28: //如果不加下面的话,当单指双击时,会先调用单指单击中的处理,再调用单指双击中的处理
29: [singleFingerOne requireGestureRecognizerToFail:singleFingerTwo];
30: //同理双指亦是如此
31: [doubleFingerOne requireGestureRecognizerToFail:doubleFingerTwo];
32:
33: [self.view addGestureRecognizer:singleFingerOne];
34: [self.view addGestureRecognizer:singleFingerTwo];
35: [self.view addGestureRecognizer:doubleFingerOne];
36: [self.view addGestureRecognizer:doubleFingerTwo];
37:
38: [singleFingerOne release];
39: [singleFingerTwo release];
40: [doubleFingerOne release];
41: [doubleFingerTwo release];

import UIKit import SnapKit class DeviceListNewViewController: SurveillanceCommonTableController { // MARK: - 属性 private lazy var titleView: UILabel = { let label = UILabel() label.text = "设备列表" label.font = .tpm20Medium() label.textColor = .tpbTextPrimary label.textAlignment = .center return label }() private lazy var searchBar: TPBSearchBar = { let bar = TPBSearchBar() bar.placeholder = "搜索" bar.backgroundColor = .clear bar.delegate = self return bar }() private lazy var searchContainerView: UIView = { let view = UIView() view.backgroundColor = UIColor(white: 0.96, alpha: 1.0) view.layer.cornerRadius = 22 view.clipsToBounds = true view.addSubview(searchBar) return view }() private lazy var tabScrollView: UIScrollView = { let scrollView = UIScrollView() scrollView.showsHorizontalScrollIndicator = false scrollView.backgroundColor = .clear scrollView.translatesAutoresizingMaskIntoConstraints = false return scrollView }() private lazy var tabButtons: [UIButton] = { return tabButtonTitles.enumerated().map { index, title in let btn = UIButton(type: .custom) btn.tag = index btn.setTitle(title, for: .normal) btn.titleLabel?.font = UIFont.tpr14Regular() btn.setTitleColor(.black, for: .normal) btn.setTitleColor(.tpbPrimary, for: .selected) btn.layer.borderWidth = 1 btn.layer.borderColor = UIColor(white: 0.85, alpha: 1.0).cgColor btn.layer.cornerRadius = 20 btn.contentEdgeInsets = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16) btn.addTarget(self, action: #selector(onTabTapped(_:)), for: .touchUpInside) return btn } }() private let tabButtonTitles = ["所有设备", "收藏页面", "站点选择"] private var selectedTabIndex = 0 // MARK: - View Setup override func tpbSetupSubviews() { super.tpbSetupSubviews() setupNav() view.addSubview(titleView) view.addSubview(searchContainerView) view.addSubview(tabScrollView) // 初始化按钮选中状态 updateTabSelection(index: selectedTabIndex) // 👇 直接在这里布局 tab 按钮(内联实现,避免找不到方法) layoutTabButtonsInScrollView() } private func setupNav() { navigationItem.leftBarButtonItem = tpbCreateLeftBarButtonItem( with: TPImageLiteral("icon_back"), andTarget: self, andAction: #selector(onBack) ) navigationItem.rightBarButtonItem = tpbCreateRightBarButtonItem( with: TPImageLiteral("icon_settings"), andTarget: self, andAction: #selector(onSettings) ) navigationItem.title = "" } override func tpbMakeConstraint() { super.tpbMakeConstraint() titleView.snp.makeConstraints { make in make.top.equalTo(view.safeAreaLayoutGuide).offset(8) make.centerX.equalToSuperview() } searchContainerView.snp.makeConstraints { make in make.top.equalTo(titleView.snp.bottom).offset(16) make.leading.trailing.equalToSuperview().inset(16) make.height.equalTo(44) // 固定高度适配 TPBSearchBar 常见大小 } searchBar.snp.makeConstraints { make in make.edges.equalTo(searchContainerView).inset(UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)) } tabScrollView.snp.makeConstraints { make in make.top.equalTo(searchContainerView.snp.bottom).offset(16) make.leading.trailing.equalToSuperview().inset(0) make.height.equalTo(40) } } // MARK: - 内联布局 Tab 按钮(防止 “Cannot find” 错误) private func layoutTabButtonsInScrollView() { // 清空之前的内容 tabScrollView.subviews.forEach { $0.removeFromSuperview() } var previousButton: UIButton? var currentX: CGFloat = 16 for button in tabButtons { tabScrollView.addSubview(button) // 使用固定 frame 或 SnapKit 设置位置 button.snp.makeConstraints { make in make.centerY.equalToSuperview() make.height.equalTo(36) if let prev = previousButton { make.leading.equalTo(prev.snp.trailing).offset(8) } else { make.leading.equalTo(tabScrollView.contentLayoutGuide).offset(16) } make.width.greaterThanOrEqualTo(80) } previousButton = button currentX += button.intrinsicContentSize.width + 8 } // 手动设置 contentSize(更稳定) DispatchQueue.main.async { var totalWidth: CGFloat = 16 for (i, btn) in self.tabButtons.enumerated() { totalWidth += btn.intrinsicContentSize.width + (i > 0 ? 8 : 0) } totalWidth += 16 self.tabScrollView.contentSize = CGSize(width: totalWidth, height: 40) } } // MARK: - 数据模型构建(仅搭建结构,不填充真实数据) private func reloadData() { var tempSectionArray = [TPBTableSectionModel]() let section0 = TPBTableSectionModel() var cellModels = [TPBBaseTableCellModel]() // Cell 1: Tab ScrollView let tabCellModel = TPBBaseTableCellModel.customContent(with: tabScrollView) cellModels.append(tabCellModel) // Cell 2: 占位用的 CollectionView 区域(仅视觉框架) let collectionViewPlaceholder = UIView() collectionViewPlaceholder.backgroundColor = .tpbBackground collectionViewPlaceholder.layer.cornerRadius = 12 collectionViewPlaceholder.clipsToBounds = true // 添加一个简单的提示标签表示“这里是列表” let placeholderLabel = UILabel() placeholderLabel.text = "设备内容区域" placeholderLabel.textColor = .tpbTextSecondaryContent placeholderLabel.font = .tpr14Regular() placeholderLabel.textAlignment = .center placeholderLabel.sizeToFit() collectionViewPlaceholder.addSubview(placeholderLabel) placeholderLabel.snp.makeConstraints { make in make.center.equalToSuperview() } let collectionCellModel = TPBBaseTableCellModel.customContent(with: collectionViewPlaceholder) collectionCellModel.height = TPBTableElementHeight.customHeight(240) cellModels.append(collectionCellModel) section0.cellModelArray = cellModels tempSectionArray.append(section0) sectionArray = tempSectionArray tableView.reloadData() } // MARK: - 生命周期 override func viewDidLoad() { super.viewDidLoad() reloadData() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // 后续绑定 collectionView delegate } // MARK: - 点击事件 @objc private func onBack() { navigationController?.popViewController(animated: true) } @objc private func onSettings() { print("Settings tapped") } @objc private func onTabTapped(_ sender: UIButton) { guard sender.tag != selectedTabIndex else { return } tabButtons[selectedTabIndex].isSelected = false selectedTabIndex = sender.tag sender.isSelected = true // 后续刷新数据 print("Switched to tab: \(selectedTabIndex)") } private func updateTabSelection(index: Int) { for (i, btn) in tabButtons.enumerated() { btn.isSelected = (i == index) } } } // MARK: - TPBSearchBarDelegate(空实现,仅满足协议要求) extension DeviceListNewViewController: TPBSearchBarDelegate { func searchBar(_ searchBar: TPBSearchBar, textDidChange searchText: String) { // TODO: 搜索关键词变化时处理(后续接入 ViewModel) } func searchBarTextDidBeginEditing(_ searchBar: TPBSearchBar) { addTapToDismissKeyboard() } func searchBarTextDidEndEditing(_ searchBar: TPBSearchBar) { removeTapToDismissKeyboard() } func searchBarSearchButtonClicked(_ searchBar: TPBSearchBar) { searchBar.resignFirstResponder() } } // MARK: - Keyboard Dismiss Gesture private extension DeviceListNewViewController { func addTapToDismissKeyboard() { let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) tap.cancelsTouchesInView = false view.addGestureRecognizer(tap) objc_setAssociatedObject( self, &AssociatedKeys.searchBarTapGestureKey, tap, .OBJC_ASSOCIATION_RETAIN_NONATOMIC ) } func removeTapToDismissKeyboard() { guard let tap = objc_getAssociatedObject(self, &AssociatedKeys.searchBarTapGestureKey) as? UITapGestureRecognizer else { return } view.removeGestureRecognizer(tap) objc_setAssociatedObject(self, &AssociatedKeys.searchBarTapGestureKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } @objc func dismissKeyboard() { searchBar.resignFirstResponder() } } // MARK: - Associated Object Keys private struct AssociatedKeys { static var searchBarTapGestureKey: UInt8 = 0 } 现在这个,给我实现成这样的效果: 屏幕靠上部分显示titleview,然后下面显示searchbar,然后下面显示收藏按钮、所有设备那个scrollview,然后在下面显示一个UIcollectionView,滚动范围就在这个UICollectionView区域,明白我想要的结构了吗?修改代码 import UIKit import SnapKit class DeviceListNewViewController: SurveillanceCommonTableController { // MARK: - 属性 private lazy var titleView: UILabel = { let label = UILabel() label.text = "设备列表" label.font = .tpm20Medium() label.textColor = .tpbTextPrimary label.textAlignment = .center return label }() private lazy var searchBar: TPBSearchBar = { let bar = TPBSearchBar() bar.placeholder = "搜索" bar.backgroundColor = .clear bar.delegate = self return bar }() private lazy var searchContainerView: UIView = { let view = UIView() view.backgroundColor = UIColor(white: 0.96, alpha: 1.0) view.layer.cornerRadius = 22 view.clipsToBounds = true view.addSubview(searchBar) return view }() private lazy var tabScrollView: UIScrollView = { let scrollView = UIScrollView() scrollView.showsHorizontalScrollIndicator = false scrollView.backgroundColor = .clear scrollView.translatesAutoresizingMaskIntoConstraints = false return scrollView }() private lazy var tabButtons: [UIButton] = { return tabButtonTitles.enumerated().map { index, title in let btn = UIButton(type: .custom) btn.tag = index btn.setTitle(title, for: .normal) btn.titleLabel?.font = UIFont.tpr14Regular() btn.setTitleColor(.black, for: .normal) btn.setTitleColor(.tpbPrimary, for: .selected) btn.layer.borderWidth = 1 btn.layer.borderColor = UIColor(white: 0.85, alpha: 1.0).cgColor btn.layer.cornerRadius = 20 btn.contentEdgeInsets = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16) btn.addTarget(self, action: #selector(onTabTapped(_:)), for: .touchUpInside) return btn } }() private let tabButtonTitles = ["所有设备", "收藏页面", "站点选择"] private var selectedTabIndex = 0 // MARK: - View Setup override func tpbSetupSubviews() { super.tpbSetupSubviews() setupNav() view.addSubview(titleView) view.addSubview(searchContainerView) view.addSubview(tabScrollView) // 初始化按钮选中状态 updateTabSelection(index: selectedTabIndex) // 👇 直接在这里布局 tab 按钮(内联实现,避免找不到方法) layoutTabButtonsInScrollView() } private func setupNav() { navigationItem.leftBarButtonItem = tpbCreateLeftBarButtonItem( with: TPImageLiteral("icon_back"), andTarget: self, andAction: #selector(onBack) ) navigationItem.rightBarButtonItem = tpbCreateRightBarButtonItem( with: TPImageLiteral("icon_settings"), andTarget: self, andAction: #selector(onSettings) ) navigationItem.title = "" } override func tpbMakeConstraint() { super.tpbMakeConstraint() titleView.snp.makeConstraints { make in make.top.equalTo(view.safeAreaLayoutGuide).offset(8) make.centerX.equalToSuperview() } searchContainerView.snp.makeConstraints { make in make.top.equalTo(titleView.snp.bottom).offset(16) make.leading.trailing.equalToSuperview().inset(16) make.height.equalTo(44) // 固定高度适配 TPBSearchBar 常见大小 } searchBar.snp.makeConstraints { make in make.edges.equalTo(searchContainerView).inset(UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)) } tabScrollView.snp.makeConstraints { make in make.top.equalTo(searchContainerView.snp.bottom).offset(16) make.leading.trailing.equalToSuperview().inset(0) make.height.equalTo(40) } } // MARK: - 内联布局 Tab 按钮(防止 “Cannot find” 错误) private func layoutTabButtonsInScrollView() { // 清空之前的内容 tabScrollView.subviews.forEach { $0.removeFromSuperview() } var previousButton: UIButton? var currentX: CGFloat = 16 for button in tabButtons { tabScrollView.addSubview(button) // 使用固定 frame 或 SnapKit 设置位置 button.snp.makeConstraints { make in make.centerY.equalToSuperview() make.height.equalTo(36) if let prev = previousButton { make.leading.equalTo(prev.snp.trailing).offset(8) } else { make.leading.equalTo(tabScrollView.contentLayoutGuide).offset(16) } make.width.greaterThanOrEqualTo(80) } previousButton = button currentX += button.intrinsicContentSize.width + 8 } // 手动设置 contentSize(更稳定) DispatchQueue.main.async { var totalWidth: CGFloat = 16 for (i, btn) in self.tabButtons.enumerated() { totalWidth += btn.intrinsicContentSize.width + (i > 0 ? 8 : 0) } totalWidth += 16 self.tabScrollView.contentSize = CGSize(width: totalWidth, height: 40) } } // MARK: - 数据模型构建(仅搭建结构,不填充真实数据) private func reloadData() { var tempSectionArray = [TPBTableSectionModel]() let section0 = TPBTableSectionModel() var cellModels = [TPBBaseTableCellModel]() // Cell 1: Tab ScrollView let tabCellModel = TPBBaseTableCellModel.customContent(with: tabScrollView) cellModels.append(tabCellModel) // Cell 2: 占位用的 CollectionView 区域(仅视觉框架) let collectionViewPlaceholder = UIView() collectionViewPlaceholder.backgroundColor = .tpbBackground collectionViewPlaceholder.layer.cornerRadius = 12 collectionViewPlaceholder.clipsToBounds = true // 添加一个简单的提示标签表示“这里是列表” let placeholderLabel = UILabel() placeholderLabel.text = "设备内容区域" placeholderLabel.textColor = .tpbTextSecondaryContent placeholderLabel.font = .tpr14Regular() placeholderLabel.textAlignment = .center placeholderLabel.sizeToFit() collectionViewPlaceholder.addSubview(placeholderLabel) placeholderLabel.snp.makeConstraints { make in make.center.equalToSuperview() } let collectionCellModel = TPBBaseTableCellModel.customContent(with: collectionViewPlaceholder) collectionCellModel.height = TPBTableElementHeight.customHeight(240) cellModels.append(collectionCellModel) section0.cellModelArray = cellModels tempSectionArray.append(section0) sectionArray = tempSectionArray tableView.reloadData() } // MARK: - 生命周期 override func viewDidLoad() { super.viewDidLoad() reloadData() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // 后续绑定 collectionView delegate } // MARK: - 点击事件 @objc private func onBack() { navigationController?.popViewController(animated: true) } @objc private func onSettings() { print("Settings tapped") } @objc private func onTabTapped(_ sender: UIButton) { guard sender.tag != selectedTabIndex else { return } tabButtons[selectedTabIndex].isSelected = false selectedTabIndex = sender.tag sender.isSelected = true // 后续刷新数据 print("Switched to tab: \(selectedTabIndex)") } private func updateTabSelection(index: Int) { for (i, btn) in tabButtons.enumerated() { btn.isSelected = (i == index) } } } // MARK: - TPBSearchBarDelegate(空实现,仅满足协议要求) extension DeviceListNewViewController: TPBSearchBarDelegate { func searchBar(_ searchBar: TPBSearchBar, textDidChange searchText: String) { // TODO: 搜索关键词变化时处理(后续接入 ViewModel) } func searchBarTextDidBeginEditing(_ searchBar: TPBSearchBar) { addTapToDismissKeyboard() } func searchBarTextDidEndEditing(_ searchBar: TPBSearchBar) { removeTapToDismissKeyboard() } func searchBarSearchButtonClicked(_ searchBar: TPBSearchBar) { searchBar.resignFirstResponder() } } // MARK: - Keyboard Dismiss Gesture private extension DeviceListNewViewController { func addTapToDismissKeyboard() { let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) tap.cancelsTouchesInView = false view.addGestureRecognizer(tap) objc_setAssociatedObject( self, &AssociatedKeys.searchBarTapGestureKey, tap, .OBJC_ASSOCIATION_RETAIN_NONATOMIC ) } func removeTapToDismissKeyboard() { guard let tap = objc_getAssociatedObject(self, &AssociatedKeys.searchBarTapGestureKey) as? UITapGestureRecognizer else { return } view.removeGestureRecognizer(tap) objc_setAssociatedObject(self, &AssociatedKeys.searchBarTapGestureKey, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } @objc func dismissKeyboard() { searchBar.resignFirstResponder() } } // MARK: - Associated Object Keys private struct AssociatedKeys { static var searchBarTapGestureKey: UInt8 = 0 }
最新发布
12-02
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值