Full Code of Tencent/QMUI_iOS for AI

master 4dca2347dcb5 cached
386 files
3.0 MB
795.1k tokens
128 symbols
1 requests
Download .txt
Showing preview only (3,172K chars total). Download the full file or copy to clipboard to get everything.
Repository: Tencent/QMUI_iOS
Branch: master
Commit: 4dca2347dcb5
Files: 386
Total size: 3.0 MB

Directory structure:
gitextract_c6ylhudb/

├── .github/
│   └── ISSUE_TEMPLATE/
│       ├── ----.md
│       ├── bug_report.md
│       └── feature_request.md
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE.TXT
├── QMUIConfigurationTemplate/
│   ├── QMUIConfigurationTemplate.h
│   └── QMUIConfigurationTemplate.m
├── QMUIKit/
│   ├── Info.plist
│   ├── PrivacyInfo.xcprivacy
│   ├── QMUIComponents/
│   │   ├── AssetLibrary/
│   │   │   ├── QMUIAsset.h
│   │   │   ├── QMUIAsset.m
│   │   │   ├── QMUIAssetsGroup.h
│   │   │   ├── QMUIAssetsGroup.m
│   │   │   ├── QMUIAssetsManager.h
│   │   │   └── QMUIAssetsManager.m
│   │   ├── CAAnimation+QMUI.h
│   │   ├── CAAnimation+QMUI.m
│   │   ├── CALayer+QMUIViewAnimation.h
│   │   ├── CALayer+QMUIViewAnimation.m
│   │   ├── ImagePickerLibrary/
│   │   │   ├── QMUIAlbumViewController.h
│   │   │   ├── QMUIAlbumViewController.m
│   │   │   ├── QMUIImagePickerCollectionViewCell.h
│   │   │   ├── QMUIImagePickerCollectionViewCell.m
│   │   │   ├── QMUIImagePickerHelper.h
│   │   │   ├── QMUIImagePickerHelper.m
│   │   │   ├── QMUIImagePickerPreviewViewController.h
│   │   │   ├── QMUIImagePickerPreviewViewController.m
│   │   │   ├── QMUIImagePickerViewController.h
│   │   │   └── QMUIImagePickerViewController.m
│   │   ├── NavigationBarTransition/
│   │   │   ├── UINavigationBar+Transition.h
│   │   │   ├── UINavigationBar+Transition.m
│   │   │   ├── UINavigationController+NavigationBarTransition.h
│   │   │   └── UINavigationController+NavigationBarTransition.m
│   │   ├── QMUIAlertController.h
│   │   ├── QMUIAlertController.m
│   │   ├── QMUIAnimation/
│   │   │   ├── QMUIAnimationHelper.h
│   │   │   ├── QMUIAnimationHelper.m
│   │   │   ├── QMUIDisplayLinkAnimation.h
│   │   │   ├── QMUIDisplayLinkAnimation.m
│   │   │   └── QMUIEasings.h
│   │   ├── QMUIAppearance.h
│   │   ├── QMUIAppearance.m
│   │   ├── QMUIBadge/
│   │   │   ├── QMUIBadgeLabel.h
│   │   │   ├── QMUIBadgeLabel.m
│   │   │   ├── QMUIBadgeProtocol.h
│   │   │   ├── UIBarItem+QMUIBadge.h
│   │   │   ├── UIBarItem+QMUIBadge.m
│   │   │   ├── UIView+QMUIBadge.h
│   │   │   └── UIView+QMUIBadge.m
│   │   ├── QMUIButton/
│   │   │   ├── QMUIButton.h
│   │   │   ├── QMUIButton.m
│   │   │   ├── QMUINavigationButton.h
│   │   │   ├── QMUINavigationButton.m
│   │   │   ├── QMUIToolbarButton.h
│   │   │   └── QMUIToolbarButton.m
│   │   ├── QMUICellHeightCache.h
│   │   ├── QMUICellHeightCache.m
│   │   ├── QMUICellHeightKeyCache/
│   │   │   ├── QMUICellHeightKeyCache.h
│   │   │   ├── QMUICellHeightKeyCache.m
│   │   │   ├── UITableView+QMUICellHeightKeyCache.h
│   │   │   └── UITableView+QMUICellHeightKeyCache.m
│   │   ├── QMUICellSizeKeyCache/
│   │   │   ├── QMUICellSizeKeyCache.h
│   │   │   ├── QMUICellSizeKeyCache.m
│   │   │   ├── UICollectionView+QMUICellSizeKeyCache.h
│   │   │   └── UICollectionView+QMUICellSizeKeyCache.m
│   │   ├── QMUICheckbox.h
│   │   ├── QMUICheckbox.m
│   │   ├── QMUICollectionViewPagingLayout.h
│   │   ├── QMUICollectionViewPagingLayout.m
│   │   ├── QMUIConsole/
│   │   │   ├── QMUIConsole.h
│   │   │   ├── QMUIConsole.m
│   │   │   ├── QMUIConsoleToolbar.h
│   │   │   ├── QMUIConsoleToolbar.m
│   │   │   ├── QMUIConsoleViewController.h
│   │   │   ├── QMUIConsoleViewController.m
│   │   │   ├── QMUILog+QMUIConsole.h
│   │   │   └── QMUILog+QMUIConsole.m
│   │   ├── QMUIDialogViewController.h
│   │   ├── QMUIDialogViewController.m
│   │   ├── QMUIEmotionInputManager.h
│   │   ├── QMUIEmotionInputManager.m
│   │   ├── QMUIEmotionView.h
│   │   ├── QMUIEmotionView.m
│   │   ├── QMUIEmptyView.h
│   │   ├── QMUIEmptyView.m
│   │   ├── QMUIFloatLayoutView.h
│   │   ├── QMUIFloatLayoutView.m
│   │   ├── QMUIGridView.h
│   │   ├── QMUIGridView.m
│   │   ├── QMUIImagePreviewView/
│   │   │   ├── QMUIImagePreviewView.h
│   │   │   ├── QMUIImagePreviewView.m
│   │   │   ├── QMUIImagePreviewViewController.h
│   │   │   ├── QMUIImagePreviewViewController.m
│   │   │   ├── QMUIImagePreviewViewTransitionAnimator.h
│   │   │   └── QMUIImagePreviewViewTransitionAnimator.m
│   │   ├── QMUIKeyboardManager.h
│   │   ├── QMUIKeyboardManager.m
│   │   ├── QMUILabel.h
│   │   ├── QMUILabel.m
│   │   ├── QMUILayouter/
│   │   │   ├── QMUILayouter.h
│   │   │   ├── QMUILayouterItem.h
│   │   │   ├── QMUILayouterItem.m
│   │   │   ├── QMUILayouterLinearHorizontal.h
│   │   │   ├── QMUILayouterLinearHorizontal.m
│   │   │   ├── QMUILayouterLinearVertical.h
│   │   │   └── QMUILayouterLinearVertical.m
│   │   ├── QMUILog/
│   │   │   ├── QMUILog.h
│   │   │   ├── QMUILogItem.h
│   │   │   ├── QMUILogItem.m
│   │   │   ├── QMUILogNameManager.h
│   │   │   ├── QMUILogNameManager.m
│   │   │   ├── QMUILogger.h
│   │   │   └── QMUILogger.m
│   │   ├── QMUILogManagerViewController.h
│   │   ├── QMUILogManagerViewController.m
│   │   ├── QMUILogger+QMUIConfigurationTemplate.h
│   │   ├── QMUILogger+QMUIConfigurationTemplate.m
│   │   ├── QMUIMarqueeLabel.h
│   │   ├── QMUIMarqueeLabel.m
│   │   ├── QMUIModalPresentationViewController.h
│   │   ├── QMUIModalPresentationViewController.m
│   │   ├── QMUIMoreOperationController.h
│   │   ├── QMUIMoreOperationController.m
│   │   ├── QMUIMultipleDelegates/
│   │   │   ├── NSObject+QMUIMultipleDelegates.h
│   │   │   ├── NSObject+QMUIMultipleDelegates.m
│   │   │   ├── QMUIMultipleDelegates.h
│   │   │   └── QMUIMultipleDelegates.m
│   │   ├── QMUINavigationTitleView.h
│   │   ├── QMUINavigationTitleView.m
│   │   ├── QMUIOrderedDictionary.h
│   │   ├── QMUIOrderedDictionary.m
│   │   ├── QMUIPieProgressView.h
│   │   ├── QMUIPieProgressView.m
│   │   ├── QMUIPopupContainerView.h
│   │   ├── QMUIPopupContainerView.m
│   │   ├── QMUIPopupMenuView/
│   │   │   ├── QMUIPopupMenuItem.h
│   │   │   ├── QMUIPopupMenuItem.m
│   │   │   ├── QMUIPopupMenuItemView.h
│   │   │   ├── QMUIPopupMenuItemView.m
│   │   │   ├── QMUIPopupMenuItemViewProtocol.h
│   │   │   ├── QMUIPopupMenuView.h
│   │   │   └── QMUIPopupMenuView.m
│   │   ├── QMUIScrollAnimator/
│   │   │   ├── QMUINavigationBarScrollingAnimator.h
│   │   │   ├── QMUINavigationBarScrollingAnimator.m
│   │   │   ├── QMUINavigationBarScrollingSnapAnimator.h
│   │   │   ├── QMUINavigationBarScrollingSnapAnimator.m
│   │   │   ├── QMUIScrollAnimator.h
│   │   │   └── QMUIScrollAnimator.m
│   │   ├── QMUISearchBar.h
│   │   ├── QMUISearchBar.m
│   │   ├── QMUISearchController.h
│   │   ├── QMUISearchController.m
│   │   ├── QMUISegmentedControl.h
│   │   ├── QMUISegmentedControl.m
│   │   ├── QMUISheetPresentation/
│   │   │   ├── QMUISheetPresentationNavigationBar.h
│   │   │   ├── QMUISheetPresentationNavigationBar.m
│   │   │   ├── QMUISheetPresentationSupports.h
│   │   │   └── QMUISheetPresentationSupports.m
│   │   ├── QMUITableView.h
│   │   ├── QMUITableView.m
│   │   ├── QMUITableViewCell.h
│   │   ├── QMUITableViewCell.m
│   │   ├── QMUITableViewHeaderFooterView.h
│   │   ├── QMUITableViewHeaderFooterView.m
│   │   ├── QMUITableViewProtocols.h
│   │   ├── QMUITestView.h
│   │   ├── QMUITestView.m
│   │   ├── QMUITextField.h
│   │   ├── QMUITextField.m
│   │   ├── QMUITextView.h
│   │   ├── QMUITextView.m
│   │   ├── QMUITheme/
│   │   │   ├── QMUITheme.h
│   │   │   ├── QMUIThemeManager.h
│   │   │   ├── QMUIThemeManager.m
│   │   │   ├── QMUIThemeManagerCenter.h
│   │   │   ├── QMUIThemeManagerCenter.m
│   │   │   ├── QMUIThemePrivate.h
│   │   │   ├── QMUIThemePrivate.m
│   │   │   ├── UIColor+QMUITheme.h
│   │   │   ├── UIColor+QMUITheme.m
│   │   │   ├── UIImage+QMUITheme.h
│   │   │   ├── UIImage+QMUITheme.m
│   │   │   ├── UIView+QMUITheme.h
│   │   │   ├── UIView+QMUITheme.m
│   │   │   ├── UIViewController+QMUITheme.h
│   │   │   ├── UIViewController+QMUITheme.m
│   │   │   ├── UIVisualEffect+QMUITheme.h
│   │   │   └── UIVisualEffect+QMUITheme.m
│   │   ├── QMUITips.h
│   │   ├── QMUITips.m
│   │   ├── QMUIWeakObjectContainer.h
│   │   ├── QMUIWeakObjectContainer.m
│   │   ├── QMUIWindowSizeMonitor.h
│   │   ├── QMUIWindowSizeMonitor.m
│   │   ├── QMUIZoomImageView.h
│   │   ├── QMUIZoomImageView.m
│   │   ├── StaticTableView/
│   │   │   ├── QMUIStaticTableViewCellData.h
│   │   │   ├── QMUIStaticTableViewCellData.m
│   │   │   ├── QMUIStaticTableViewCellDataSource.h
│   │   │   ├── QMUIStaticTableViewCellDataSource.m
│   │   │   ├── UITableView+QMUIStaticCell.h
│   │   │   └── UITableView+QMUIStaticCell.m
│   │   └── ToastView/
│   │       ├── QMUIToastAnimator.h
│   │       ├── QMUIToastAnimator.m
│   │       ├── QMUIToastBackgroundView.h
│   │       ├── QMUIToastBackgroundView.m
│   │       ├── QMUIToastContentView.h
│   │       ├── QMUIToastContentView.m
│   │       ├── QMUIToastView.h
│   │       └── QMUIToastView.m
│   ├── QMUICore/
│   │   ├── QMUICommonDefines.h
│   │   ├── QMUIConfiguration.h
│   │   ├── QMUIConfiguration.m
│   │   ├── QMUIConfigurationMacros.h
│   │   ├── QMUICore.h
│   │   ├── QMUIHelper.h
│   │   ├── QMUIHelper.m
│   │   ├── QMUILab.h
│   │   ├── QMUIRuntime.h
│   │   └── QMUIRuntime.m
│   ├── QMUIKit.h
│   ├── QMUIMainFrame/
│   │   ├── QMUICommonTableViewController.h
│   │   ├── QMUICommonTableViewController.m
│   │   ├── QMUICommonViewController.h
│   │   ├── QMUICommonViewController.m
│   │   ├── QMUINavigationController.h
│   │   ├── QMUINavigationController.m
│   │   ├── QMUITabBarViewController.h
│   │   └── QMUITabBarViewController.m
│   ├── QMUIResources/
│   │   └── Images.xcassets/
│   │       ├── Contents.json
│   │       ├── QMUI_checkbox16.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_checkbox16_checked.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_checkbox16_disabled.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_checkbox16_indeterminate.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_console_clear.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_console_filter.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_console_filter_selected.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_console_logo.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_emotion_delete.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_hiddenAlbum.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_icloud_download_fault.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_pickerImage_checkbox.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_pickerImage_checkbox_checked.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_pickerImage_favorite.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_pickerImage_video_mark.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_previewImage_checkbox.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_previewImage_checkbox_checked.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_tips_done.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_tips_error.imageset/
│   │       │   └── Contents.json
│   │       └── QMUI_tips_info.imageset/
│   │           └── Contents.json
│   └── UIKitExtensions/
│       ├── CALayer+QMUI.h
│       ├── CALayer+QMUI.m
│       ├── NSArray+QMUI.h
│       ├── NSArray+QMUI.m
│       ├── NSAttributedString+QMUI.h
│       ├── NSAttributedString+QMUI.m
│       ├── NSCharacterSet+QMUI.h
│       ├── NSCharacterSet+QMUI.m
│       ├── NSDictionary+QMUI.h
│       ├── NSDictionary+QMUI.m
│       ├── NSMethodSignature+QMUI.h
│       ├── NSMethodSignature+QMUI.m
│       ├── NSNumber+QMUI.h
│       ├── NSNumber+QMUI.m
│       ├── NSObject+QMUI.h
│       ├── NSObject+QMUI.m
│       ├── NSParagraphStyle+QMUI.h
│       ├── NSParagraphStyle+QMUI.m
│       ├── NSPointerArray+QMUI.h
│       ├── NSPointerArray+QMUI.m
│       ├── NSRegularExpression+QMUI.h
│       ├── NSRegularExpression+QMUI.m
│       ├── NSShadow+QMUI.h
│       ├── NSShadow+QMUI.m
│       ├── NSString+QMUI.h
│       ├── NSString+QMUI.m
│       ├── NSURL+QMUI.h
│       ├── NSURL+QMUI.m
│       ├── QMUIBarProtocol/
│       │   ├── QMUIBarProtocol.h
│       │   ├── QMUIBarProtocolPrivate.h
│       │   ├── QMUIBarProtocolPrivate.m
│       │   ├── UINavigationBar+QMUIBarProtocol.h
│       │   ├── UINavigationBar+QMUIBarProtocol.m
│       │   ├── UITabBar+QMUIBarProtocol.h
│       │   └── UITabBar+QMUIBarProtocol.m
│       ├── QMUIStringPrivate.h
│       ├── QMUIStringPrivate.m
│       ├── UIActivityIndicatorView+QMUI.h
│       ├── UIActivityIndicatorView+QMUI.m
│       ├── UIApplication+QMUI.h
│       ├── UIApplication+QMUI.m
│       ├── UIBarItem+QMUI.h
│       ├── UIBarItem+QMUI.m
│       ├── UIBezierPath+QMUI.h
│       ├── UIBezierPath+QMUI.m
│       ├── UIBlurEffect+QMUI.h
│       ├── UIBlurEffect+QMUI.m
│       ├── UIButton+QMUI.h
│       ├── UIButton+QMUI.m
│       ├── UICollectionView+QMUI.h
│       ├── UICollectionView+QMUI.m
│       ├── UICollectionViewCell+QMUI.h
│       ├── UICollectionViewCell+QMUI.m
│       ├── UIColor+QMUI.h
│       ├── UIColor+QMUI.m
│       ├── UIControl+QMUI.h
│       ├── UIControl+QMUI.m
│       ├── UIFont+QMUI.h
│       ├── UIFont+QMUI.m
│       ├── UIGestureRecognizer+QMUI.h
│       ├── UIGestureRecognizer+QMUI.m
│       ├── UIImage+QMUI.h
│       ├── UIImage+QMUI.m
│       ├── UIImageView+QMUI.h
│       ├── UIImageView+QMUI.m
│       ├── UIInterface+QMUI.h
│       ├── UIInterface+QMUI.m
│       ├── UILabel+QMUI.h
│       ├── UILabel+QMUI.m
│       ├── UIMenuController+QMUI.h
│       ├── UIMenuController+QMUI.m
│       ├── UINavigationBar+QMUI.h
│       ├── UINavigationBar+QMUI.m
│       ├── UINavigationController+QMUI.h
│       ├── UINavigationController+QMUI.m
│       ├── UINavigationItem+QMUI.h
│       ├── UINavigationItem+QMUI.m
│       ├── UIScrollView+QMUI.h
│       ├── UIScrollView+QMUI.m
│       ├── UISearchBar+QMUI.h
│       ├── UISearchBar+QMUI.m
│       ├── UISearchController+QMUI.h
│       ├── UISearchController+QMUI.m
│       ├── UISlider+QMUI.h
│       ├── UISlider+QMUI.m
│       ├── UISwitch+QMUI.h
│       ├── UISwitch+QMUI.m
│       ├── UITabBar+QMUI.h
│       ├── UITabBar+QMUI.m
│       ├── UITabBarItem+QMUI.h
│       ├── UITabBarItem+QMUI.m
│       ├── UITableView+QMUI.h
│       ├── UITableView+QMUI.m
│       ├── UITableViewCell+QMUI.h
│       ├── UITableViewCell+QMUI.m
│       ├── UITableViewHeaderFooterView+QMUI.h
│       ├── UITableViewHeaderFooterView+QMUI.m
│       ├── UITextField+QMUI.h
│       ├── UITextField+QMUI.m
│       ├── UITextInputTraits+QMUI.h
│       ├── UITextInputTraits+QMUI.m
│       ├── UITextView+QMUI.h
│       ├── UITextView+QMUI.m
│       ├── UIToolbar+QMUI.h
│       ├── UIToolbar+QMUI.m
│       ├── UITraitCollection+QMUI.h
│       ├── UITraitCollection+QMUI.m
│       ├── UIView+QMUI.h
│       ├── UIView+QMUI.m
│       ├── UIView+QMUIBorder.h
│       ├── UIView+QMUIBorder.m
│       ├── UIViewController+QMUI.h
│       ├── UIViewController+QMUI.m
│       ├── UIVisualEffectView+QMUI.h
│       ├── UIVisualEffectView+QMUI.m
│       ├── UIWindow+QMUI.h
│       └── UIWindow+QMUI.m
├── QMUIKit.podspec
├── QMUIKitTests/
│   ├── Components/
│   │   └── QMUIThemeTests.m
│   ├── Core/
│   │   └── QMUICommonDefinesTests.m
│   ├── Info.plist
│   └── UIKitExtensions/
│       ├── NSObjectTests.m
│       ├── NSStringTests.m
│       ├── UIButtonTests.m
│       └── UIColorTests.m
├── README.md
├── add_license.py
├── new_license_content.txt
├── old_license_content.txt
├── qmui.xcodeproj/
│   ├── project.pbxproj
│   ├── project.xcworkspace/
│   │   ├── contents.xcworkspacedata
│   │   └── xcshareddata/
│   │       └── IDEWorkspaceChecks.plist
│   └── xcshareddata/
│       └── xcschemes/
│           ├── QMUIKit.xcscheme
│           └── QMUIKitTests.xcscheme
└── umbrellaHeaderFileCreator.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE/----.md
================================================
---
name: 使用方式
about: 咨询 QMUI 某些功能的用法
title: ''
labels: help wanted
assignees: ''

---

QMUI 已经提供了**详尽的注释文档**,以及**完整的示例项目 [QMUI Demo](https://github.com/QMUI/QMUI_iOS_Demo)**,当你遇到某些功能不知道怎么使用,或者想知道 QMUI 是否有提供某些功能时,请先查看注释文档或者 Demo,找不到了再提 issue。若提的 issue 已有明确注释或示例的,**可能会被直接关闭或得不到及时的回复**,请知悉。


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug
about: QMUIKit 框架本身的 bug(请注意区分非 QMUIKit 代码引发的问题)
title: ''
labels: ''
assignees: ''

---

**Bug 表现**
问题的具体描述

**截图**
Bug 现场的界面截图,或者 Xcode 控制台的错误信息截图,有问题的代码截图

**如何重现**
1. ...
2. ...

**预期的表现**
正常情况下,应该是什么表现

**其他信息**
 - 设备: [例如模拟器、iPhone、iPad]
 - iOS 版本: [iOS 14.x]
 - Xcode 版本: [Xcode 12.x]
 - QMUI 版本: [4.x.x]


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: 意见与建议
about: 功能、接口设计等的相关建议
title: ''
labels: suggest
assignees: ''

---

**现存问题或期望目标**
对于功能的建议,请说明具体的场景,现在的代码为什么无法实现需求。
对于代码设计方面的建议,请说明目前的问题所在。


================================================
FILE: .gitignore
================================================
.DS_Store
xcuserdata/




================================================
FILE: CONTRIBUTING.md
================================================
[腾讯开源激励计划](https://opensource.tencent.com/contribution) 鼓励开发者的参与和贡献,期待你的加入。我们欢迎 [report Issues](https://github.com/QMUI/QMUI_iOS/issues) 或者 [pull requests](https://github.com/QMUI/QMUI_iOS/pulls)。 在贡献代码之前请阅读以下指引。

## 问题管理
我们用 Github Issues 去跟踪 public bugs 和 feature requests。

### 使用 Issues

1. 新建 issues 前,请查找已存在或者相类似的 issue,从而保证不存在冗余。
2. 新建 issues 时,请根据我们提供的 issue 模板,尽可能提供详细的描述、截屏或者短视频来辅助我们定位问题。

###  Pull Requests

我们欢迎大家为 QMUI_iOS 贡献代码,在完成一个 pull request 之前请确认:

1. 从 `master` fork 你自己的分支。
2. 在修改了代码之后请修改对应的文档和注释。
3. 在新建的文件中请加入 licence 和 copy right 声明。
4. 确保一致的代码风格。
5. 做充分的测试。


================================================
FILE: LICENSE.TXT
================================================
Tencent is pleased to support the open source community by making QMUI_iOS available.  
Copyright (C) 2016-2021 THL A29 Limited, a Tencent company.  All rights reserved.
If you have downloaded a copy of the QMUI_iOS binary from Tencent, please note that the QMUI_iOS binary is licensed under the MIT License.
If you have downloaded a copy of the QMUI_iOS source code from Tencent, please note that QMUI_iOS source code is licensed under the MIT License.  Your integration of QMUI_iOS into your own projects may require compliance with the MIT License.
A copy of the MIT License is included in this file.


Terms of the MIT License:
---------------------------------------------------
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: QMUIConfigurationTemplate/QMUIConfigurationTemplate.h
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

//
//  QMUIConfigurationTemplate.h
//
//  Created by QMUI Team on 15/3/29.
//

#import <Foundation/Foundation.h>
#import <QMUIKit/QMUIKit.h>

/**
 *  QMUIConfigurationTemplate 是一份配置表,用于配合 QMUIConfiguration 来管理整个 App 的全局样式,使用方式:
 *  在 QMUI 项目代码的文件夹里找到 QMUIConfigurationTemplate 目录,把里面所有文件复制到自己项目里,保证能被编译到即可,不需要在某些地方 import,也不需要手动运行。
 *
 *  @warning 更新 QMUIKit 的版本时,请留意 Release Log 里是否有提醒更新配置表,请尽量保持自己项目里的配置表与 QMUIKit 里的配置表一致,避免遗漏新的属性。
 *  @warning 配置表的 class 名必须以 QMUIConfigurationTemplate 开头,并且实现 <QMUIConfigurationTemplateProtocol>,因为这两者是 QMUI 识别该 NSObject 是否为一份配置表的条件。
 *  @warning QMUI 2.3.0 之后,配置表改为自动运行,不需要再在某个地方手动运行了。
 */
@interface QMUIConfigurationTemplate : NSObject <QMUIConfigurationTemplateProtocol>

@end


================================================
FILE: QMUIConfigurationTemplate/QMUIConfigurationTemplate.m
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

//
//  QMUIConfigurationTemplate.m
//  qmui
//
//  Created by QMUI Team on 15/3/29.
//

#import "QMUIConfigurationTemplate.h"
#import <QMUIKit/QMUIKit.h>

@implementation QMUIConfigurationTemplate

#pragma mark - <QMUIConfigurationTemplateProtocol>

- (void)applyConfigurationTemplate {
    
    // === 修改配置值 === //
    
    #pragma mark - Global Color
    
    QMUICMI.clearColor = UIColorMakeWithRGBA(255, 255, 255, 0);                 // UIColorClear : 透明色
    QMUICMI.whiteColor = UIColorMake(255, 255, 255);                            // UIColorWhite : 白色(不用 [UIColor whiteColor] 是希望保持颜色空间为 RGB)
    QMUICMI.blackColor = UIColorMake(0, 0, 0);                                  // UIColorBlack : 黑色(不用 [UIColor blackColor] 是希望保持颜色空间为 RGB)
    QMUICMI.grayColor = UIColorMake(179, 179, 179);                             // UIColorGray  : 最常用的灰色
    QMUICMI.grayDarkenColor = UIColorMake(163, 163, 163);                       // UIColorGrayDarken : 深一点的灰色
    QMUICMI.grayLightenColor = UIColorMake(198, 198, 198);                      // UIColorGrayLighten : 浅一点的灰色
    QMUICMI.redColor = UIColorMake(250, 58, 58);                                // UIColorRed : 红色
    QMUICMI.greenColor = UIColorMake(159, 214, 97);                             // UIColorGreen : 绿色
    QMUICMI.blueColor = UIColorMake(49, 189, 243);                              // UIColorBlue : 蓝色
    QMUICMI.yellowColor = UIColorMake(255, 207, 71);                            // UIColorYellow : 黄色
    
    QMUICMI.linkColor = UIColorMake(56, 116, 171);                              // UIColorLink : 文字链接颜色
    QMUICMI.disabledColor = UIColorGray;                                        // UIColorDisabled : 全局 disabled 的颜色,一般用于 UIControl 等控件
    QMUICMI.backgroundColor = nil;                                              // UIColorForBackground : 界面背景色,默认用于 QMUICommonViewController.view 的背景色
    QMUICMI.maskDarkColor = UIColorMakeWithRGBA(0, 0, 0, .35f);                 // UIColorMask : 深色的背景遮罩,默认用于 QMAlertController、QMUIDialogViewController 等弹出控件的遮罩
    QMUICMI.maskLightColor = UIColorMakeWithRGBA(255, 255, 255, .5f);           // UIColorMaskWhite : 浅色的背景遮罩,QMUIKit 里默认没用到,只是占个位
    QMUICMI.separatorColor = UIColorMake(222, 224, 226);                        // UIColorSeparator : 全局默认的分割线颜色,默认用于列表分隔线颜色、UIView (QMUIBorder) 分隔线颜色
    QMUICMI.separatorDashedColor = UIColorMake(17, 17, 17);                     // UIColorSeparatorDashed : 全局默认的虚线分隔线的颜色,默认 QMUIKit 暂时没用到
    QMUICMI.placeholderColor = UIColorMake(196, 200, 208);                      // UIColorPlaceholder,全局的输入框的 placeholder 颜色,默认用于 QMUITextField、QMUITextView,不影响系统 UIKit 的输入框
    
    // 测试用的颜色
    QMUICMI.testColorRed = UIColorMakeWithRGBA(255, 0, 0, .3);
    QMUICMI.testColorGreen = UIColorMakeWithRGBA(0, 255, 0, .3);
    QMUICMI.testColorBlue = UIColorMakeWithRGBA(0, 0, 255, .3);
    
    #pragma mark - QMUILog
    QMUICMI.shouldPrintDefaultLog = YES;                                        // ShouldPrintDefaultLog : 是否允许输出 QMUILogLevelDefault 级别的 log
    QMUICMI.shouldPrintInfoLog = YES;                                           // ShouldPrintInfoLog : 是否允许输出 QMUILogLevelInfo 级别的 log
    QMUICMI.shouldPrintWarnLog = YES;                                           // ShouldPrintWarnLog : 是否允许输出 QMUILogLevelWarn 级别的 log
    QMUICMI.shouldPrintQMUIWarnLogToConsole = NO;                              // ShouldPrintQMUIWarnLogToConsole : 是否在出现 QMUILogWarn 时自动把这些 log 以 QMUIConsole 的方式显示到设备屏幕上
    
    #pragma mark - UIControl
    
    QMUICMI.controlHighlightedAlpha = 0.5f;                                     // UIControlHighlightedAlpha : UIControl 系列控件在 highlighted 时的 alpha,默认用于 QMUIButton、 QMUINavigationTitleView
    QMUICMI.controlDisabledAlpha = 0.5f;                                        // UIControlDisabledAlpha : UIControl 系列控件在 disabled 时的 alpha,默认用于 QMUIButton
    
    #pragma mark - UIButton
    QMUICMI.buttonHighlightedAlpha = UIControlHighlightedAlpha;                 // ButtonHighlightedAlpha : QMUIButton 在 highlighted 时的 alpha,不影响系统的 UIButton
    QMUICMI.buttonDisabledAlpha = UIControlDisabledAlpha;                       // ButtonDisabledAlpha : QMUIButton 在 disabled 时的 alpha,不影响系统的 UIButton
    QMUICMI.buttonTintColor = UIColorBlue;                                      // ButtonTintColor : QMUIButton 默认的 tintColor,不影响系统的 UIButton
    
    #pragma mark - TextInput
    QMUICMI.textFieldTextColor = nil;                                           // TextFieldTextColor : QMUITextField、QMUITextView 的 textColor,不影响 UIKit 的输入框
    QMUICMI.textFieldTintColor = nil;                                           // TextFieldTintColor : QMUITextField、QMUITextView 的 tintColor,不影响 UIKit 的输入框
    QMUICMI.textFieldTextInsets = UIEdgeInsetsMake(0, 7, 0, 7);                 // TextFieldTextInsets : QMUITextField 的内边距,不影响 UITextField
    QMUICMI.keyboardAppearance = UIKeyboardAppearanceDefault;                   // KeyboardAppearance : UITextView、UITextField、UISearchBar 的 keyboardAppearance
    
    #pragma mark - UISwitch
    QMUICMI.switchOnTintColor = nil;                                            // SwitchOnTintColor : UISwitch 打开时的背景色(除了圆点外的其他颜色)
    QMUICMI.switchOffTintColor = nil;                                           // SwitchOffTintColor : UISwitch 关闭时的背景色(除了圆点外的其他颜色)
    QMUICMI.switchThumbTintColor = nil;                                         // SwitchThumbTintColor : UISwitch 中间的操控圆点的颜色
    
    #pragma mark - NavigationBar
    
    if (@available(iOS 15.0, *)) {
        QMUICMI.navBarUsesStandardAppearanceOnly = NO;                         // NavBarUsesStandardAppearanceOnly : 对于 iOS 15 的系统,UINavigationBar 的样式分为滚动前和滚动后,虽然系统的注释里说了如果没设置 scrollEdgeAppearance 则会用 standardAppearance 代替,但实际运行效果是 scrollEdgeAppearance 默认并不会保持与 standardAppearance 一致,所以这里提供一个开关,允许你在打开开关时让 QMUI 帮你同步 standardAppearance 的值,以使 App 保持与 iOS 14 相同的效果。如需打开该开关,请保证在其他 NavBar 开关之前设置。
    }
    QMUICMI.navBarContainerClasses = nil;                                       // NavBarContainerClasses : NavigationBar 系列开关被用于 UIAppearance 时的生效范围(默认情况下除了用于 UIAppearance 外,还用于实现了 QMUINavigationControllerAppearanceDelegate 的 UIViewController),默认为 nil。当赋值为 nil 或者空数组时等效于 @[UINavigationController.class],也即对所有 UINavigationBar 生效,包括系统的通讯录(ContactsUI.framework)、打印等。当值不为空时,获取 UINavigationBar 的 appearance 请使用 UINavigationBar.qmui_appearanceConfigured 方法代替系统的 UINavigationBar.appearance。请保证这个配置项先于其他任意 NavBar 配置项执行。
    QMUICMI.navBarHighlightedAlpha = 0.2f;                                      // NavBarHighlightedAlpha : QMUINavigationButton 在 highlighted 时的 alpha
    QMUICMI.navBarDisabledAlpha = 0.2f;                                         // NavBarDisabledAlpha : QMUINavigationButton 在 disabled 时的 alpha
    QMUICMI.navBarButtonFont = nil;                                             // NavBarButtonFont : UINavigationBar 里 UIBarButtonItem 以及 QMUINavigationButtonTypeNormal 的字体
    QMUICMI.navBarButtonFontBold = nil;                                         // NavBarButtonFontBold : iOS 15 及以后用于设置 UINavigationBar 里 Done 类型的 UIBarButtonItem 以及 QMUINavigationButtonTypeBold 的字体,iOS 14 及以前只对后者生效
    QMUICMI.navBarBackgroundImage = nil;                                        // NavBarBackgroundImage : UINavigationBar 的背景图
    if (@available(iOS 15.0, *)) {
        QMUICMI.navBarRemoveBackgroundEffectAutomatically = NO;                 // NavBarRemoveBackgroundEffectAutomatically : iOS 15 及以后,QMUI 里的 UINavigationBar 使用的是 UINavigationBarAppearance 来设置样式,新方式默认是 backgroundImage 和 backgroundEffect 共存的,而 iOS 14 及以前的旧方式,一旦设置了 backgroundImage 则 backgroundEffect 自动会被移除,因此提供该开关允许业务将行为回退到 iOS 14 及以前的效果。默认为 NO。
    }
    QMUICMI.navBarShadowImage = nil;                                            // NavBarShadowImage : UINavigationBar.shadowImage,也即导航栏底部那条分隔线,配合 NavBarShadowImageColor 使用。
    QMUICMI.navBarShadowImageColor = nil;                                       // NavBarShadowImageColor : UINavigationBar.shadowImage 的颜色,如果为 nil,则使用 NavBarShadowImage 的值,如果 NavBarShadowImage 也为 nil,则使用系统默认的分隔线。如果不为 nil,而 NavBarShadowImage 为 nil,则自动创建一张 1px 高的图并将其设置为 NavBarShadowImageColor 的颜色然后设置上去,如果 NavBarShadowImage 不为 nil 且 renderingMode 不为 UIImageRenderingModeAlwaysOriginal,则将 NavBarShadowImage 设置为 NavBarShadowImageColor 的颜色然后设置上去。
    QMUICMI.navBarBarTintColor = nil;                                           // NavBarBarTintColor : UINavigationBar.barTintColor,也即背景色
    QMUICMI.navBarStyle = UIBarStyleDefault;                                    // NavBarStyle : UINavigationBar 的 barStyle
    QMUICMI.navBarTintColor = nil;                                              // NavBarTintColor : NavBarContainerClasses 里的 UINavigationBar 的 tintColor,也即导航栏上面的按钮颜色
    QMUICMI.navBarTitleColor = nil;                                             // NavBarTitleColor : UINavigationBar 的标题颜色,以及 QMUINavigationTitleView 的默认文字颜色
    QMUICMI.navBarTitleFont = nil;                                              // NavBarTitleFont : UINavigationBar 的标题字体,以及 QMUINavigationTitleView 的默认字体
    QMUICMI.navBarLargeTitleColor = nil;                                        // NavBarLargeTitleColor : UINavigationBar 在大标题模式下的标题颜色
    QMUICMI.navBarLargeTitleFont = nil;                                         // NavBarLargeTitleFont : UINavigationBar 在大标题模式下的标题字体
    QMUICMI.navBarBackButtonTitlePositionAdjustment = UIOffsetZero;             // NavBarBarBackButtonTitlePositionAdjustment : 导航栏返回按钮的文字偏移
    QMUICMI.sizeNavBarBackIndicatorImageAutomatically = YES;                    // SizeNavBarBackIndicatorImageAutomatically : 是否要自动调整 NavBarBackIndicatorImage 的 size 为 (13, 21)
    QMUICMI.navBarBackIndicatorImage = nil;                                     // NavBarBackIndicatorImage : 导航栏的返回按钮的图片,图片尺寸建议为(13, 21),否则最终的图片位置无法与系统原生的位置保持一致
    QMUICMI.navBarCloseButtonImage = [UIImage qmui_imageWithShape:QMUIImageShapeNavClose size:CGSizeMake(16, 16) tintColor:NavBarTintColor];     // NavBarCloseButtonImage : QMUINavigationButton 用到的 × 的按钮图片
    
    QMUICMI.navBarLoadingMarginRight = 3;                                       // NavBarLoadingMarginRight : QMUINavigationTitleView 里左边 loading 的右边距
    QMUICMI.navBarAccessoryViewMarginLeft = 5;                                  // NavBarAccessoryViewMarginLeft : QMUINavigationTitleView 里右边 accessoryView 的左边距
    QMUICMI.navBarActivityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;// NavBarActivityIndicatorViewStyle : QMUINavigationTitleView 里左边 loading 的主题
    QMUICMI.navBarAccessoryViewTypeDisclosureIndicatorImage = [[UIImage qmui_imageWithShape:QMUIImageShapeTriangle size:CGSizeMake(8, 5) tintColor:nil] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];     // NavBarAccessoryViewTypeDisclosureIndicatorImage : QMUINavigationTitleView 右边箭头的图片
    
    #pragma mark - TabBar
    
    if (@available(iOS 15.0, *)) {
        QMUICMI.tabBarUsesStandardAppearanceOnly = NO;                          // TabBarUsesStandardAppearanceOnly : 对于 iOS 15 的系统,UITabBar 的样式分为滚动前和滚动后,虽然系统的注释里说了如果没设置 scrollEdgeAppearance 则会用 standardAppearance 代替,但实际运行效果是 scrollEdgeAppearance 默认并不会保持与 standardAppearance 一致,所以这里提供一个开关,允许你在打开开关时让 QMUI 帮你同步 standardAppearance 的值,以使 App 保持与 iOS 14 相同的效果。如需打开该开关,请保证在其他 NavBar 开关之前设置。
    }
    QMUICMI.tabBarContainerClasses = nil;                                       // TabBarContainerClasses : TabBar 系列开关的生效范围,默认为 nil,当赋值为 nil 或者空数组时等效于 @[UITabBarController.class],也即对所有 UITabBar 生效。当值不为空时,获取 UITabBar 的 appearance 请使用 UITabBar.qmui_appearanceConfigured 方法代替系统的 UITabBar.appearance。请保证这个配置项先于其他任意 TabBar 配置项执行。
    QMUICMI.tabBarBackgroundImage = nil;                                        // TabBarBackgroundImage : UITabBar 的背景图
    if (@available(iOS 15.0, *)) {
        QMUICMI.tabBarRemoveBackgroundEffectAutomatically = NO;                 // TabBarRemoveBackgroundEffectAutomatically : iOS 15 及以后,QMUI 里的 UITabBar 使用的是 UITabBarAppearance 来设置样式,新方式默认是 backgroundImage 和 backgroundEffect 共存的,而 iOS 14 及以前的旧方式,一旦设置了 backgroundImage 则 backgroundEffect 自动会被移除,因此提供该开关允许业务将行为回退到 iOS 14 及以前的效果。默认为 NO。
    }
    QMUICMI.tabBarBarTintColor = nil;                                           // TabBarBarTintColor : UITabBar 的 barTintColor,如果需要看到磨砂效果则应该提供半透明的色值
    QMUICMI.tabBarShadowImageColor = nil;                                       // TabBarShadowImageColor : UITabBar 的 shadowImage 的颜色,会自动创建一张 1px 高的图片
    QMUICMI.tabBarStyle = UIBarStyleDefault;                                    // TabBarStyle : UITabBar 的 barStyle
    QMUICMI.tabBarItemTitleFont = nil;                                          // TabBarItemTitleFont : UITabBarItem 的标题字体
    QMUICMI.tabBarItemTitleFontSelected = nil;                                  // TabBarItemTitleFontSelected : 选中的 UITabBarItem 的标题字体
    QMUICMI.tabBarItemTitleColor = nil;                                         // TabBarItemTitleColor : 未选中的 UITabBarItem 的标题颜色
    QMUICMI.tabBarItemTitleColorSelected = nil;                                 // TabBarItemTitleColorSelected : 选中的 UITabBarItem 的标题颜色
    QMUICMI.tabBarItemImageColor = nil;                                         // TabBarItemImageColor : UITabBarItem 未选中时的图片颜色
    QMUICMI.tabBarItemImageColorSelected = nil;                                 // TabBarItemImageColorSelected : UITabBarItem 选中时的图片颜色
    
    #pragma mark - Toolbar
    
    if (@available(iOS 15.0, *)) {
        QMUICMI.toolBarUsesStandardAppearanceOnly = NO;                          // ToolBarUsesStandardAppearanceOnly : 对于 iOS 15 的系统,UIToolbar 的样式分为滚动前和滚动后,虽然系统的注释里说了如果没设置 scrollEdgeAppearance 则会用 standardAppearance 代替,但实际运行效果是 scrollEdgeAppearance 默认并不会保持与 standardAppearance 一致,所以这里提供一个开关,允许你在打开开关时让 QMUI 帮你同步 standardAppearance 的值,以使 App 保持与 iOS 14 相同的效果。如需打开该开关,请保证在其他 NavBar 开关之前设置。
    }
    QMUICMI.toolBarContainerClasses = nil;                                      // ToolBarContainerClasses : ToolBar 系列开关的生效范围,默认为 nil,当赋值为 nil 或者空数组时等效于 @[UINavigationController.class],也即对所有 UIToolbar 生效。当值不为空时,获取 UIToolbar 的 appearance 请使用 UIToolbar.qmui_appearanceConfigured 方法代替系统的 UIToolbar.appearance。请保证这个配置项先于其他任意 ToolBar 配置项执行。
    QMUICMI.toolBarHighlightedAlpha = 0.4f;                                     // ToolBarHighlightedAlpha : QMUIToolbarButton 在 highlighted 状态下的 alpha
    QMUICMI.toolBarDisabledAlpha = 0.4f;                                        // ToolBarDisabledAlpha : QMUIToolbarButton 在 disabled 状态下的 alpha
    QMUICMI.toolBarTintColor = nil;                                             // ToolBarTintColor : NavBarContainerClasses 里的 UIToolbar 的 tintColor,以及 QMUIToolbarButton normal 状态下的文字颜色
    QMUICMI.toolBarTintColorHighlighted = [ToolBarTintColor colorWithAlphaComponent:ToolBarHighlightedAlpha];   // ToolBarTintColorHighlighted : QMUIToolbarButton 在 highlighted 状态下的文字颜色
    QMUICMI.toolBarTintColorDisabled = [ToolBarTintColor colorWithAlphaComponent:ToolBarDisabledAlpha];         // ToolBarTintColorDisabled : QMUIToolbarButton 在 disabled 状态下的文字颜色
    QMUICMI.toolBarBackgroundImage = nil;                                       // ToolBarBackgroundImage : NavBarContainerClasses 里的 UIToolbar 的背景图
    if (@available(iOS 15.0, *)) {
        QMUICMI.toolBarRemoveBackgroundEffectAutomatically = NO;                // ToolBarRemoveBackgroundEffectAutomatically : iOS 15 及以后,QMUI 里的 UIToolbar 使用的是 UIToolbarAppearance 来设置样式,新方式默认是 backgroundImage 和 backgroundEffect 共存的,而 iOS 14 及以前的旧方式,一旦设置了 backgroundImage 则 backgroundEffect 自动会被移除,因此提供该开关允许业务将行为回退到 iOS 14 及以前的效果。默认为 NO。
    }
    QMUICMI.toolBarBarTintColor = nil;                                          // ToolBarBarTintColor : NavBarContainerClasses 里的 UIToolbar 的 tintColor
    QMUICMI.toolBarShadowImageColor = nil;                                      // ToolBarShadowImageColor : NavBarContainerClasses 里的 UIToolbar 的 shadowImage 的颜色,会自动创建一张 1px 高的图片
    QMUICMI.toolBarStyle = UIBarStyleDefault;                                   // ToolBarStyle : NavBarContainerClasses 里的 UIToolbar 的 barStyle
    QMUICMI.toolBarButtonFont = nil;                                            // ToolBarButtonFont : QMUIToolbarButton 的字体
    
    #pragma mark - SearchBar
    
    QMUICMI.searchBarTextFieldBackgroundImage = nil;                            // SearchBarTextFieldBackgroundImage : QMUISearchBar 里的文本框的背景图,图片高度会决定输入框的高度
    QMUICMI.searchBarTextFieldBorderColor = nil;                                // SearchBarTextFieldBorderColor : QMUISearchBar 里的文本框的边框颜色
    QMUICMI.searchBarTextFieldCornerRadius = 2.0;                               // SearchBarTextFieldCornerRadius : QMUISearchBar 里的文本框的圆角大小,-1 表示圆角大小为输入框高度的一半
    QMUICMI.searchBarBackgroundImage = nil;                                     // SearchBarBackgroundImage : 搜索框的背景图,如果需要设置底部分隔线的颜色也请绘制到图片里
    QMUICMI.searchBarTintColor = nil;                                           // SearchBarTintColor : QMUISearchBar 的 tintColor,也即上面的操作控件的主题色
    QMUICMI.searchBarTextColor = nil;                                           // SearchBarTextColor : QMUISearchBar 里的文本框的文字颜色
    QMUICMI.searchBarPlaceholderColor = UIColorPlaceholder;                     // SearchBarPlaceholderColor : QMUISearchBar 里的文本框的 placeholder 颜色
    QMUICMI.searchBarFont = nil;                                                // SearchBarFont : QMUISearchBar 里的文本框的文字字体及 placeholder 的字体
    QMUICMI.searchBarSearchIconImage = nil;                                     // SearchBarSearchIconImage : QMUISearchBar 里的放大镜 icon
    QMUICMI.searchBarClearIconImage = nil;                                      // SearchBarClearIconImage : QMUISearchBar 里的文本框输入文字时右边的清空按钮的图片
    
    #pragma mark - Plain TableView
    
    QMUICMI.tableViewEstimatedHeightEnabled = YES;                              // TableViewEstimatedHeightEnabled : 是否要开启全局 UITableView 的 estimatedRow(Section/Footer)Height
    
    QMUICMI.tableViewBackgroundColor = nil;                                     // TableViewBackgroundColor : Plain 类型的 QMUITableView 的背景色颜色
    QMUICMI.tableSectionIndexColor = nil;                                       // TableSectionIndexColor : 列表右边的字母索引条的文字颜色
    QMUICMI.tableSectionIndexBackgroundColor = nil;                             // TableSectionIndexBackgroundColor : 列表右边的字母索引条的背景色
    QMUICMI.tableSectionIndexTrackingBackgroundColor = nil;                     // TableSectionIndexTrackingBackgroundColor : 列表右边的字母索引条在选中时的背景色
    QMUICMI.tableViewSeparatorColor = UIColorSeparator;                         // TableViewSeparatorColor : 列表的分隔线颜色
    
    QMUICMI.tableViewCellNormalHeight = UITableViewAutomaticDimension;          // TableViewCellNormalHeight : QMUITableView 的默认 cell 高度
    QMUICMI.tableViewCellTitleLabelColor = nil;                                 // TableViewCellTitleLabelColor : QMUITableViewCell 的 textLabel 的文字颜色
    QMUICMI.tableViewCellDetailLabelColor = nil;                                // TableViewCellDetailLabelColor : QMUITableViewCell 的 detailTextLabel 的文字颜色
    QMUICMI.tableViewCellBackgroundColor = nil;                                 // TableViewCellBackgroundColor : QMUITableViewCell 的背景色
    QMUICMI.tableViewCellSelectedBackgroundColor = UIColorMake(238, 239, 241);  // TableViewCellSelectedBackgroundColor : QMUITableViewCell 点击时的背景色
    QMUICMI.tableViewCellWarningBackgroundColor = UIColorYellow;                // TableViewCellWarningBackgroundColor : QMUITableViewCell 用于表示警告时的背景色,备用
    QMUICMI.tableViewCellDisclosureIndicatorImage = nil;                        // TableViewCellDisclosureIndicatorImage : QMUITableViewCell 当 accessoryType 为 UITableViewCellAccessoryDisclosureIndicator 时的箭头的图片
    QMUICMI.tableViewCellCheckmarkImage = nil;                                  // TableViewCellCheckmarkImage : QMUITableViewCell 当 accessoryType 为 UITableViewCellAccessoryCheckmark 时的打钩的图片
    QMUICMI.tableViewCellDetailButtonImage = nil; // TableViewCellDetailButtonImage : QMUITableViewCell 当 accessoryType 为 UITableViewCellAccessoryDetailButton 或 UITableViewCellAccessoryDetailDisclosureButton 时右边的 i 按钮图片
    QMUICMI.tableViewCellSpacingBetweenDetailButtonAndDisclosureIndicator = 12; // TableViewCellSpacingBetweenDetailButtonAndDisclosureIndicator : 列表 cell 右边的 i 按钮和向右箭头之间的间距(仅当两者都使用了自定义图片并且同时显示时才生效)
    
    QMUICMI.tableViewSectionHeaderBackgroundColor = UIColorMake(244, 244, 244);                         // TableViewSectionHeaderBackgroundColor : Plain 类型的 QMUITableView sectionHeader 的背景色
    QMUICMI.tableViewSectionFooterBackgroundColor = UIColorMake(244, 244, 244);                         // TableViewSectionFooterBackgroundColor : Plain 类型的 QMUITableView sectionFooter 的背景色
    QMUICMI.tableViewSectionHeaderFont = UIFontBoldMake(12);                                            // TableViewSectionHeaderFont : Plain 类型的 QMUITableView sectionHeader 里的文字字体
    QMUICMI.tableViewSectionFooterFont = UIFontBoldMake(12);                                            // TableViewSectionFooterFont : Plain 类型的 QMUITableView sectionFooter 里的文字字体
    QMUICMI.tableViewSectionHeaderTextColor = UIColorGrayDarken;                                        // TableViewSectionHeaderTextColor : Plain 类型的 QMUITableView sectionHeader 里的文字颜色
    QMUICMI.tableViewSectionFooterTextColor = UIColorGray;                                              // TableViewSectionFooterTextColor : Plain 类型的 QMUITableView sectionFooter 里的文字颜色
    QMUICMI.tableViewSectionHeaderAccessoryMargins = UIEdgeInsetsMake(0, 15, 0, 0);                     // TableViewSectionHeaderAccessoryMargins : Plain 类型的 QMUITableView sectionHeader accessoryView 的间距
    QMUICMI.tableViewSectionFooterAccessoryMargins = UIEdgeInsetsMake(0, 15, 0, 0);                     // TableViewSectionFooterAccessoryMargins : Plain 类型的 QMUITableView sectionFooter accessoryView 的间距
    QMUICMI.tableViewSectionHeaderContentInset = UIEdgeInsetsMake(4, 15, 4, 15);                        // TableViewSectionHeaderContentInset : Plain 类型的 QMUITableView sectionHeader 里的内容的 padding
    QMUICMI.tableViewSectionFooterContentInset = UIEdgeInsetsMake(4, 15, 4, 15);                        // TableViewSectionFooterContentInset : Plain 类型的 QMUITableView sectionFooter 里的内容的 padding
    if (@available(iOS 15, *)) {
        QMUICMI.tableViewSectionHeaderTopPadding = UITableViewAutomaticDimension;                       // TableViewSectionHeaderTopPadding : Plain 类型的 QMUITableView 在 iOS 15 上的 sectionHeaderTopPadding 值,仅当存在 sectionHeader 时才有效,系统的默认值为 UITableViewAutomaticDimension,表现出来是22pt的空隙
    }
    
    #pragma mark - Grouped TableView
    QMUICMI.tableViewGroupedBackgroundColor = nil;                                                      // TableViewGroupedBackgroundColor : Grouped 类型的 QMUITableView 的背景色
    QMUICMI.tableViewGroupedSeparatorColor = TableViewSeparatorColor;                                   // TableViewGroupedSeparatorColor : Grouped 类型的 QMUITableView 分隔线颜色
    QMUICMI.tableViewGroupedCellTitleLabelColor = TableViewCellTitleLabelColor;                         // TableViewGroupedCellTitleLabelColor : Grouped 类型的 QMUITableView cell 里的标题颜色
    QMUICMI.tableViewGroupedCellDetailLabelColor = TableViewCellDetailLabelColor;                       // TableViewGroupedCellDetailLabelColor : Grouped 类型的 QMUITableView cell 里的副标题颜色
    QMUICMI.tableViewGroupedCellBackgroundColor = TableViewCellBackgroundColor;                         // TableViewGroupedCellBackgroundColor : Grouped 类型的 QMUITableView cell 背景色
    QMUICMI.tableViewGroupedCellSelectedBackgroundColor = TableViewCellSelectedBackgroundColor;         // TableViewGroupedCellSelectedBackgroundColor : Grouped 类型的 QMUITableView cell 点击时的背景色
    QMUICMI.tableViewGroupedCellWarningBackgroundColor = TableViewCellWarningBackgroundColor;           // tableViewGroupedCellWarningBackgroundColor : Grouped 类型的 QMUITableView cell 在提醒状态下的背景色
    QMUICMI.tableViewGroupedSectionHeaderFont = UIFontMake(12);                                         // TableViewGroupedSectionHeaderFont : Grouped 类型的 QMUITableView sectionHeader 里的文字字体
    QMUICMI.tableViewGroupedSectionFooterFont = UIFontMake(12);                                         // TableViewGroupedSectionFooterFont : Grouped 类型的 QMUITableView sectionFooter 里的文字字体
    QMUICMI.tableViewGroupedSectionHeaderTextColor = UIColorGrayDarken;                                 // TableViewGroupedSectionHeaderTextColor : Grouped 类型的 QMUITableView sectionHeader 里的文字颜色
    QMUICMI.tableViewGroupedSectionFooterTextColor = UIColorGray;                                       // TableViewGroupedSectionFooterTextColor : Grouped 类型的 QMUITableView sectionFooter 里的文字颜色
    QMUICMI.tableViewGroupedSectionHeaderAccessoryMargins = UIEdgeInsetsMake(0, 15, 0, 0);                     // TableViewGroupedSectionHeaderAccessoryMargins : Grouped 类型的 QMUITableView sectionHeader accessoryView 的间距
    QMUICMI.tableViewGroupedSectionFooterAccessoryMargins = UIEdgeInsetsMake(0, 15, 0, 0);                     // TableViewGroupedSectionFooterAccessoryMargins : Grouped 类型的 QMUITableView sectionFooter accessoryView 的间距
    QMUICMI.tableViewGroupedSectionHeaderDefaultHeight = UITableViewAutomaticDimension;                 // TableViewGroupedSectionHeaderDefaultHeight : Grouped 类型的 QMUITableView sectionHeader 的默认高度(也即没使用自定义的 sectionHeaderView 时的高度),注意如果不需要间距,请用 CGFLOAT_MIN
    QMUICMI.tableViewGroupedSectionFooterDefaultHeight = UITableViewAutomaticDimension;                 // TableViewGroupedSectionFooterDefaultHeight : Grouped 类型的 QMUITableView sectionFooter 的默认高度(也即没使用自定义的 sectionFooterView 时的高度),注意如果不需要间距,请用 CGFLOAT_MIN
    QMUICMI.tableViewGroupedSectionHeaderContentInset = UIEdgeInsetsMake(16, 15, 8, 15);                // TableViewGroupedSectionHeaderContentInset : Grouped 类型的 QMUITableView sectionHeader 里的内容的 padding
    QMUICMI.tableViewGroupedSectionFooterContentInset = UIEdgeInsetsMake(8, 15, 2, 15);                 // TableViewGroupedSectionFooterContentInset : Grouped 类型的 QMUITableView sectionFooter 里的内容的 padding
    if (@available(iOS 15, *)) {
        QMUICMI.tableViewGroupedSectionHeaderTopPadding = UITableViewAutomaticDimension;                       // TableViewGroupedSectionHeaderTopPadding : Grouped 类型的 QMUITableView 在 iOS 15 上的 sectionHeaderTopPadding 值,仅当存在 sectionHeader 时才有效,系统的默认值为 UITableViewAutomaticDimension,表现出来是0。
    }
    
    #pragma mark - InsetGrouped TableView
    QMUICMI.tableViewInsetGroupedCornerRadius = 10;                                                     // TableViewInsetGroupedCornerRadius : InsetGrouped 类型的 UITableView 内 cell 的圆角值
    QMUICMI.tableViewInsetGroupedHorizontalInset = PreferredValueForVisualDevice(20, 15);               // TableViewInsetGroupedHorizontalInset: InsetGrouped 类型的 UITableView 内的左右缩进值
    QMUICMI.tableViewInsetGroupedBackgroundColor = TableViewGroupedBackgroundColor;                                                 // TableViewInsetGroupedBackgroundColor : InsetGrouped 类型的 UITableView 的背景色
    QMUICMI.tableViewInsetGroupedSeparatorColor = TableViewGroupedSeparatorColor;                                   // TableViewInsetGroupedSeparatorColor : InsetGrouped 类型的 QMUITableView 分隔线颜色
    QMUICMI.tableViewInsetGroupedCellTitleLabelColor = TableViewGroupedCellTitleLabelColor;                         // TableViewInsetGroupedCellTitleLabelColor : InsetGrouped 类型的 QMUITableView cell 里的标题颜色
    QMUICMI.tableViewInsetGroupedCellDetailLabelColor = TableViewGroupedCellDetailLabelColor;                       // TableViewInsetGroupedCellDetailLabelColor : InsetGrouped 类型的 QMUITableView cell 里的副标题颜色
    QMUICMI.tableViewInsetGroupedCellBackgroundColor = TableViewGroupedCellBackgroundColor;                         // TableViewInsetGroupedCellBackgroundColor : InsetGrouped 类型的 QMUITableView cell 背景色
    QMUICMI.tableViewInsetGroupedCellSelectedBackgroundColor = TableViewGroupedCellSelectedBackgroundColor;         // TableViewInsetGroupedCellSelectedBackgroundColor : InsetGrouped 类型的 QMUITableView cell 点击时的背景色
    QMUICMI.tableViewInsetGroupedCellWarningBackgroundColor = TableViewGroupedCellWarningBackgroundColor;           // TableViewInsetGroupedCellWarningBackgroundColor : InsetGrouped 类型的 QMUITableView cell 在提醒状态下的背景色
    QMUICMI.tableViewInsetGroupedSectionHeaderFont = TableViewGroupedSectionHeaderFont;                                         // TableViewInsetGroupedSectionHeaderFont : InsetGrouped 类型的 QMUITableView sectionHeader 里的文字字体
    QMUICMI.tableViewInsetGroupedSectionFooterFont = TableViewInsetGroupedSectionHeaderFont;                                         // TableViewInsetGroupedSectionFooterFont : InsetGrouped 类型的 QMUITableView sectionFooter 里的文字字体
    QMUICMI.tableViewInsetGroupedSectionHeaderTextColor = TableViewGroupedSectionHeaderTextColor;                                 // TableViewInsetGroupedSectionHeaderTextColor : InsetGrouped 类型的 QMUITableView sectionHeader 里的文字颜色
    QMUICMI.tableViewInsetGroupedSectionFooterTextColor = TableViewInsetGroupedSectionHeaderTextColor;                                       // TableViewInsetGroupedSectionFooterTextColor : InsetGrouped 类型的 QMUITableView sectionFooter 里的文字颜色
    QMUICMI.tableViewInsetGroupedSectionHeaderAccessoryMargins = TableViewGroupedSectionHeaderAccessoryMargins;                     // TableViewInsetGroupedSectionHeaderAccessoryMargins : InsetGrouped 类型的 QMUITableView sectionHeader accessoryView 的间距
    QMUICMI.tableViewInsetGroupedSectionFooterAccessoryMargins = TableViewInsetGroupedSectionHeaderAccessoryMargins;                     // TableViewInsetGroupedSectionFooterAccessoryMargins : InsetGrouped 类型的 QMUITableView sectionFooter accessoryView 的间距
    QMUICMI.tableViewInsetGroupedSectionHeaderDefaultHeight = TableViewGroupedSectionHeaderDefaultHeight;                 // TableViewInsetGroupedSectionHeaderDefaultHeight : InsetGrouped 类型的 QMUITableView sectionHeader 的默认高度(也即没使用自定义的 sectionHeaderView 时的高度),注意如果不需要间距,请用 CGFLOAT_MIN
    QMUICMI.tableViewInsetGroupedSectionFooterDefaultHeight = TableViewGroupedSectionFooterDefaultHeight;                 // TableViewInsetGroupedSectionFooterDefaultHeight : InsetGrouped 类型的 QMUITableView sectionFooter 的默认高度(也即没使用自定义的 sectionFooterView 时的高度),注意如果不需要间距,请用 CGFLOAT_MIN
    QMUICMI.tableViewInsetGroupedSectionHeaderContentInset = TableViewGroupedSectionHeaderContentInset;                // TableViewInsetGroupedSectionHeaderContentInset : InsetGrouped 类型的 QMUITableView sectionHeader 里的内容的 padding
    QMUICMI.tableViewInsetGroupedSectionFooterContentInset = TableViewInsetGroupedSectionHeaderContentInset;                 // TableViewInsetGroupedSectionFooterContentInset : InsetGrouped 类型的 QMUITableView sectionFooter 里的内容的 padding
    if (@available(iOS 15, *)) {
        QMUICMI.tableViewInsetGroupedSectionHeaderTopPadding = UITableViewAutomaticDimension;                       // TableViewInsetGroupedSectionHeaderTopPadding : InsetGrouped 类型的 QMUITableView 在 iOS 15 上的 sectionHeaderTopPadding 值,仅当存在 sectionHeader 时才有效,系统的默认值为 UITableViewAutomaticDimension,表现出来是0。
    }
    
    #pragma mark - UIWindowLevel
    QMUICMI.windowLevelQMUIAlertView = UIWindowLevelAlert - 4.0;                // UIWindowLevelQMUIAlertView : QMUIModalPresentationViewController、QMUIPopupContainerView 里使用的 UIWindow 的 windowLevel
    QMUICMI.windowLevelQMUIConsole = 1;                                         // UIWindowLevelQMUIConsole : QMUIConsole 内部的 UIWindow 的 windowLevel
    
    #pragma mark - QMUIBadge
    
    QMUICMI.badgeBackgroundColor = UIColorRed;                                  // BadgeBackgroundColor : QMUIBadge 上的未读数的背景色
    QMUICMI.badgeTextColor = UIColorWhite;                                      // BadgeTextColor : QMUIBadge 上的未读数的文字颜色
    QMUICMI.badgeFont = UIFontBoldMake(11);                                     // BadgeFont : QMUIBadge 上的未读数的字体
    QMUICMI.badgeContentEdgeInsets = UIEdgeInsetsMake(2, 4, 2, 4);              // BadgeContentEdgeInsets : QMUIBadge 上的未读数与圆圈之间的 padding
    QMUICMI.badgeOffset = CGPointMake(-9, 11);                                  // BadgeOffset : QMUIBadge 上的未读数相对于目标 view 右上角的偏移
    QMUICMI.badgeOffsetLandscape = CGPointMake(-9, 6);                          // BadgeOffsetLandscape : QMUIBadge 上的未读数在横屏下相对于目标 view 右上角的偏移
    
    QMUICMI.updatesIndicatorColor = UIColorRed;                                 // UpdatesIndicatorColor : QMUIBadge 上的未读红点的颜色
    QMUICMI.updatesIndicatorSize = CGSizeMake(7, 7);                            // UpdatesIndicatorSize : QMUIBadge 上的未读红点的大小
    QMUICMI.updatesIndicatorOffset = CGPointMake(4, UpdatesIndicatorSize.height);// UpdatesIndicatorOffset : QMUIBadge 未读红点相对于目标 view 右上角的偏移
    QMUICMI.updatesIndicatorOffsetLandscape = UpdatesIndicatorOffset;           // UpdatesIndicatorOffsetLandscape : QMUIBadge 未读红点在横屏下相对于目标 view 右上角的偏移
    
    #pragma mark - Others
    
    QMUICMI.automaticCustomNavigationBarTransitionStyle = NO;                   // AutomaticCustomNavigationBarTransitionStyle : 界面 push/pop 时是否要自动根据两个界面的 barTintColor/backgroundImage/shadowImage 的样式差异来决定是否使用自定义的导航栏效果
    QMUICMI.supportedOrientationMask = UIInterfaceOrientationMaskAll;           // SupportedOrientationMask : 默认支持的横竖屏方向
    QMUICMI.automaticallyRotateDeviceOrientation = NO;                          // AutomaticallyRotateDeviceOrientation : 是否在界面切换或 viewController.supportedOrientationMask 发生变化时自动旋转屏幕(仅 iOS 15 及以前版本需要,iOS 16 系统会自动处理,该开关无意义。)
    QMUICMI.defaultStatusBarStyle = UIStatusBarStyleDefault;                    // DefaultStatusBarStyle : 默认的状态栏样式,默认值为 UIStatusBarStyleDefault,也即在 iOS 12 及以前是黑色文字,iOS 13 及以后会自动根据当前 App 是否处于 Dark Mode 切换颜色。如果你希望固定为白色,请设置为 UIStatusBarStyleLightContent,固定黑色则设置为 UIStatusBarStyleDarkContent。
    QMUICMI.needsBackBarButtonItemTitle = YES;                                  // NeedsBackBarButtonItemTitle : 全局是否需要返回按钮的 title,不需要则只显示一个返回image
    QMUICMI.hidesBottomBarWhenPushedInitially = NO;                             // HidesBottomBarWhenPushedInitially : QMUICommonViewController.hidesBottomBarWhenPushed 的初始值,默认为 NO,以保持与系统默认值一致,但通常建议改为 YES,因为一般只有 tabBar 首页那几个界面要求为 NO
    QMUICMI.preventConcurrentNavigationControllerTransitions = YES;             // PreventConcurrentNavigationControllerTransitions : 自动保护 QMUINavigationController 在上一次 push/pop 尚未结束的时候就进行下一次 push/pop 的行为,避免产生 crash
    QMUICMI.navigationBarHiddenInitially = NO;                                  // NavigationBarHiddenInitially : QMUINavigationControllerDelegate preferredNavigationBarHidden 的初始值,默认为NO
    QMUICMI.shouldFixTabBarSafeAreaInsetsBug = NO;                              // ShouldFixTabBarSafeAreaInsetsBug : 是否要对 iOS 11 及以后的版本修复当存在 UITabBar 时,UIScrollView 的 inset.bottom 可能错误的 bug(issue #218 #934),默认为 YES
    QMUICMI.shouldFixSearchBarMaskViewLayoutBug = NO;                           // ShouldFixSearchBarMaskViewLayoutBug : 是否自动修复 UISearchController.searchBar 被当作 tableHeaderView 使用时可能出现的布局 bug(issue #950)
    QMUICMI.shouldPrintQMUIWarnLogToConsole = IS_DEBUG;                         // ShouldPrintQMUIWarnLogToConsole : 是否在出现 QMUILogWarn 时自动把这些 log 以 QMUIConsole 的方式显示到设备屏幕上
    QMUICMI.dynamicPreferredValueForIPad = NO;                                  // DynamicPreferredValueForIPad : 当 iPad 处于 Slide Over 或 Split View 分屏模式下,宏 `PreferredValueForXXX` 是否把 iPad 视为某种屏幕宽度近似的 iPhone 来取值。
    QMUICMI.ignoreKVCAccessProhibited = NO;                                     // IgnoreKVCAccessProhibited : 是否全局忽略 iOS 13 对 KVC 访问 UIKit 私有属性的限制
    QMUICMI.adjustScrollIndicatorInsetsByContentInsetAdjustment = NO;           // AdjustScrollIndicatorInsetsByContentInsetAdjustment : 当将 UIScrollView.contentInsetAdjustmentBehavior 设为 UIScrollViewContentInsetAdjustmentNever 时,是否自动将 UIScrollView.automaticallyAdjustsScrollIndicatorInsets 设为 NO,以保证原本在 iOS 12 下的代码不用修改就能在 iOS 13 下正常控制滚动条的位置。
}

// QMUI 2.3.0 版本里,配置表新增这个方法,返回 YES 表示在 App 启动时要自动应用这份配置表。仅当你的 App 里存在多份配置表时,才需要把除默认配置表之外的其他配置表的返回值改为 NO。
- (BOOL)shouldApplyTemplateAutomatically {
    return YES;
}

@end


================================================
FILE: QMUIKit/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>en</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>$(PRODUCT_NAME)</string>
	<key>CFBundlePackageType</key>
	<string>FMWK</string>
	<key>CFBundleShortVersionString</key>
	<string>$(MARKETING_VERSION)</string>
	<key>CFBundleVersion</key>
	<string>$(CURRENT_PROJECT_VERSION)</string>
	<key>NSPrincipalClass</key>
	<string></string>
</dict>
</plist>


================================================
FILE: QMUIKit/PrivacyInfo.xcprivacy
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>NSPrivacyAccessedAPITypes</key>
	<array>
		<dict>
			<key>NSPrivacyAccessedAPIType</key>
			<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
			<key>NSPrivacyAccessedAPITypeReasons</key>
			<array>
				<string>CA92.1</string>
			</array>
		</dict>
	</array>
	<key>NSPrivacyCollectedDataTypes</key>
	<array/>
	<key>NSPrivacyTrackingDomains</key>
	<array/>
	<key>NSPrivacyTracking</key>
	<false/>
</dict>
</plist>


================================================
FILE: QMUIKit/QMUIComponents/AssetLibrary/QMUIAsset.h
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

//
//  QMUIAsset.h
//  qmui
//
//  Created by QMUI Team on 15/6/30.
//

#import <UIKit/UIKit.h>
#import <Photos/PHImageManager.h>

typedef NS_ENUM(NSUInteger, QMUIAssetType) {
    QMUIAssetTypeUnknow,
    QMUIAssetTypeImage,
    QMUIAssetTypeVideo,
    QMUIAssetTypeAudio
};

typedef NS_ENUM(NSUInteger, QMUIAssetSubType) {
    QMUIAssetSubTypeUnknow,
    QMUIAssetSubTypeImage,
    QMUIAssetSubTypeLivePhoto NS_ENUM_AVAILABLE_IOS(9_1),
    QMUIAssetSubTypeGIF
};

/// Status when download asset from iCloud
typedef NS_ENUM(NSUInteger, QMUIAssetDownloadStatus) {
    QMUIAssetDownloadStatusSucceed,
    QMUIAssetDownloadStatusDownloading,
    QMUIAssetDownloadStatusCanceled,
    QMUIAssetDownloadStatusFailed
};


@class PHAsset;

/**
 *  相册里某一个资源的包装对象,该资源可能是图片、视频等。
 *  @note QMUIAsset 重写了 isEqual: 方法,只要两个 QMUIAsset 的 identifier 相同,则认为是同一个对象,以方便在数组、字典等容器中对大量 QMUIAsset 进行遍历查找等操作。
 */
@interface QMUIAsset : NSObject

@property(nonatomic, assign, readonly) QMUIAssetType assetType;
@property(nonatomic, assign, readonly) QMUIAssetSubType assetSubType;

- (instancetype)initWithPHAsset:(PHAsset *)phAsset;

@property(nonatomic, strong, readonly) PHAsset *phAsset;
@property(nonatomic, assign, readonly) QMUIAssetDownloadStatus downloadStatus; // 从 iCloud 下载资源大图的状态
@property(nonatomic, assign) double downloadProgress; // 从 iCloud 下载资源大图的进度
@property(nonatomic, assign) NSInteger requestID; // 从 iCloud 请求获得资源的大图的请求 ID
@property (nonatomic, copy, readonly) NSString *identifier;// Asset 的标识,每个 QMUIAsset 的 identifier 都不同。只要两个 QMUIAsset 的 identifier 相同则认为它们是同一个 asset

/// Asset 的原图(包含系统相册“编辑”功能处理后的效果)
- (UIImage *)originImage;

/**
 *  Asset 的缩略图
 *
 *  @param size 指定返回的缩略图的大小,pt 为单位
 *
 *  @return Asset 的缩略图
 */
- (UIImage *)thumbnailWithSize:(CGSize)size;

/**
 *  Asset 的预览图
 *
 *  @warning 输出与当前设备屏幕大小相同尺寸的图片,如果图片原图小于当前设备屏幕的尺寸,则只输出原图大小的图片
 *  @return Asset 的全屏图
 */
- (UIImage *)previewImage;

/**
 *  异步请求 Asset 的原图,包含了系统照片“编辑”功能处理后的效果(剪裁,旋转和滤镜等),可能会有网络请求
 *
 *  @param completion        完成请求后调用的 block,参数中包含了请求的原图以及图片信息,这个 block 会被多次调用,
 *                           其中第一次调用获取到的尺寸很小的低清图,然后不断调用,直到获取到高清图。
 *  @param phProgressHandler 处理请求进度的 handler,不在主线程上执行,在 block 中修改 UI 时注意需要手工放到主线程处理。
 *
 *  @return 返回请求图片的请求 id
 */
- (NSInteger)requestOriginImageWithCompletion:(void (^)(UIImage *result, NSDictionary<NSString *, id> *info))completion withProgressHandler:(PHAssetImageProgressHandler)phProgressHandler;

/**
 *  异步请求 Asset 的缩略图,不会产生网络请求
 *
 *  @param size       指定返回的缩略图的大小
 *  @param completion 完成请求后调用的 block,参数中包含了请求的缩略图以及图片信息,这个 block 会被多次调用,
 *                    其中第一次调用获取到的尺寸很小的低清图,然后不断调用,直到获取到高清图,这时 block 中的第二个参数(图片信息)返回的为 nil。
 *
 *  @return 返回请求图片的请求 id
 */
- (NSInteger)requestThumbnailImageWithSize:(CGSize)size completion:(void (^)(UIImage *result, NSDictionary<NSString *, id> *info))completion;

/**
 *  异步请求 Asset 的预览图,可能会有网络请求
 *
 *  @param completion        完成请求后调用的 block,参数中包含了请求的预览图以及图片信息,这个 block 会被多次调用,
 *                           其中第一次调用获取到的尺寸很小的低清图,然后不断调用,直到获取到高清图。
 *  @param phProgressHandler 处理请求进度的 handler,不在主线程上执行,在 block 中修改 UI 时注意需要手工放到主线程处理。
 *
 *  @return 返回请求图片的请求 id
 */
- (NSInteger)requestPreviewImageWithCompletion:(void (^)(UIImage *result, NSDictionary<NSString *, id> *info))completion withProgressHandler:(PHAssetImageProgressHandler)phProgressHandler;

/**
 *  异步请求 Live Photo,可能会有网络请求
 *
 *  @param completion        完成请求后调用的 block,参数中包含了请求的 Live Photo 以及相关信息,若 assetType 不是 QMUIAssetTypeLivePhoto 则为 nil
 *  @param phProgressHandler 处理请求进度的 handler,不在主线程上执行,在 block 中修改 UI 时注意需要手工放到主线程处理。
 *
 *  @warning iOS 9.1 以下中并没有 Live Photo,因此无法获取有效结果。
 *
 *  @return 返回请求图片的请求 id
 */
- (NSInteger)requestLivePhotoWithCompletion:(void (^)(PHLivePhoto *livePhoto, NSDictionary<NSString *, id> *info))completion withProgressHandler:(PHAssetImageProgressHandler)phProgressHandler;

/**
 *  异步请求 AVPlayerItem,可能会有网络请求
 *
 *  @param completion        完成请求后调用的 block,参数中包含了请求的 AVPlayerItem 以及相关信息,若 assetType 不是 QMUIAssetTypeVideo 则为 nil
 *  @param phProgressHandler 处理请求进度的 handler,不在主线程上执行,在 block 中修改 UI 时注意需要手工放到主线程处理。
 *
 *  @return 返回请求 AVPlayerItem 的请求 id
 */
- (NSInteger)requestPlayerItemWithCompletion:(void (^)(AVPlayerItem *playerItem, NSDictionary<NSString *, id> *info))completion withProgressHandler:(PHAssetVideoProgressHandler)phProgressHandler;

/**
 *  异步请求图片的 Data
 *
 *  @param completion 完成请求后调用的 block,参数中包含了请求的图片 Data(若 assetType 不是 QMUIAssetTypeImage 或 QMUIAssetTypeLivePhoto 则为 nil),该图片是否为 GIF 的判断值,以及该图片的文件格式是否为 HEIC
 */
- (void)requestImageData:(void (^)(NSData *imageData, NSDictionary<NSString *, id> *info, BOOL isGIF, BOOL isHEIC))completion;

/**
 * 获取图片的 UIImageOrientation 值,仅 assetType 为 QMUIAssetTypeImage 或 QMUIAssetTypeLivePhoto 时有效
 */
- (UIImageOrientation)imageOrientation;

/// 更新下载资源的结果
- (void)updateDownloadStatusWithDownloadResult:(BOOL)succeed;

/**
 * 获取 Asset 的体积(数据大小)
 */
- (void)assetSize:(void (^)(long long size))completion;

- (NSTimeInterval)duration;

@end


================================================
FILE: QMUIKit/QMUIComponents/AssetLibrary/QMUIAsset.m
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

//
//  QMUIAsset.m
//  qmui
//
//  Created by QMUI Team on 15/6/30.
//

#import "QMUIAsset.h"
#import <Photos/Photos.h>
#import <CoreServices/CoreServices.h>
#import "QMUICore.h"
#import "QMUIAssetsManager.h"
#import "NSString+QMUI.h"

static NSString * const kAssetInfoImageData = @"imageData";
static NSString * const kAssetInfoOriginInfo = @"originInfo";
static NSString * const kAssetInfoDataUTI = @"dataUTI";
static NSString * const kAssetInfoOrientation = @"orientation";
static NSString * const kAssetInfoSize = @"size";

@interface QMUIAsset ()

@property(nonatomic, copy) NSDictionary *phAssetInfo;
@end

@implementation QMUIAsset {
    PHAsset *_phAsset;
    float imageSize;
}

- (instancetype)initWithPHAsset:(PHAsset *)phAsset {
    if (self = [super init]) {
        _phAsset = phAsset;
        switch (phAsset.mediaType) {
            case PHAssetMediaTypeImage:
                _assetType = QMUIAssetTypeImage;
                if ([[phAsset qmui_valueForKey:@"uniformTypeIdentifier"] isEqualToString:(__bridge NSString *)kUTTypeGIF]) {
                    _assetSubType = QMUIAssetSubTypeGIF;
                } else {
                    if (phAsset.mediaSubtypes & PHAssetMediaSubtypePhotoLive) {
                        _assetSubType = QMUIAssetSubTypeLivePhoto;
                    } else {
                        _assetSubType = QMUIAssetSubTypeImage;
                    }
                }
                break;
            case PHAssetMediaTypeVideo:
                _assetType = QMUIAssetTypeVideo;
                break;
            case PHAssetMediaTypeAudio:
                _assetType = QMUIAssetTypeAudio;
                break;
            default:
                _assetType = QMUIAssetTypeUnknow;
                break;
        }
    }
    return self;
}

- (PHAsset *)phAsset {
    return _phAsset;
}

- (UIImage *)originImage {
    __block UIImage *resultImage = nil;
    PHImageRequestOptions *phImageRequestOptions = [[PHImageRequestOptions alloc] init];
    phImageRequestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
    phImageRequestOptions.networkAccessAllowed = YES;
    phImageRequestOptions.synchronous = YES;
    [[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestImageDataForAsset:_phAsset options:phImageRequestOptions resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
        resultImage = [UIImage imageWithData:imageData];
    }];
    return resultImage;
}

- (UIImage *)thumbnailWithSize:(CGSize)size {
    __block UIImage *resultImage;
    PHImageRequestOptions *phImageRequestOptions = [[PHImageRequestOptions alloc] init];
    phImageRequestOptions.networkAccessAllowed = YES;
    phImageRequestOptions.resizeMode = PHImageRequestOptionsResizeModeFast;
        // 在 PHImageManager 中,targetSize 等 size 都是使用 px 作为单位,因此需要对targetSize 中对传入的 Size 进行处理,宽高各自乘以 ScreenScale,从而得到正确的图片
    [[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestImageForAsset:_phAsset
                                                                          targetSize:CGSizeMake(size.width * ScreenScale, size.height * ScreenScale)
                                                                         contentMode:PHImageContentModeAspectFill options:phImageRequestOptions
                                                                       resultHandler:^(UIImage *result, NSDictionary *info) {
                                                                           resultImage = result;
                                                                       }];

    return resultImage;
}

- (UIImage *)previewImage {
    __block UIImage *resultImage = nil;
    PHImageRequestOptions *imageRequestOptions = [[PHImageRequestOptions alloc] init];
    imageRequestOptions.networkAccessAllowed = YES;
    imageRequestOptions.synchronous = YES;
    [[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestImageForAsset:_phAsset
                                                                        targetSize:CGSizeMake(SCREEN_WIDTH, SCREEN_HEIGHT)
                                                                       contentMode:PHImageContentModeAspectFill
                                                                           options:imageRequestOptions
                                                                     resultHandler:^(UIImage *result, NSDictionary *info) {
                                                                         resultImage = result;
                                                                     }];
    return resultImage;
}

- (NSInteger)requestOriginImageWithCompletion:(void (^)(UIImage *result, NSDictionary<NSString *, id> *info))completion withProgressHandler:(PHAssetImageProgressHandler)phProgressHandler {
    PHImageRequestOptions *imageRequestOptions = [[PHImageRequestOptions alloc] init];
    imageRequestOptions.networkAccessAllowed = YES; // 允许访问网络
    imageRequestOptions.progressHandler = phProgressHandler;
    return [[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestImageDataForAsset:_phAsset options:imageRequestOptions resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
        if (completion) {
            completion([UIImage imageWithData:imageData], info);
        }
    }];
}

- (NSInteger)requestThumbnailImageWithSize:(CGSize)size completion:(void (^)(UIImage *result, NSDictionary<NSString *, id> *info))completion {
    PHImageRequestOptions *imageRequestOptions = [[PHImageRequestOptions alloc] init];
    imageRequestOptions.resizeMode = PHImageRequestOptionsResizeModeFast;
    imageRequestOptions.networkAccessAllowed = YES;
    // 在 PHImageManager 中,targetSize 等 size 都是使用 px 作为单位,因此需要对targetSize 中对传入的 Size 进行处理,宽高各自乘以 ScreenScale,从而得到正确的图片
    return [[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestImageForAsset:_phAsset targetSize:CGSizeMake(size.width * ScreenScale, size.height * ScreenScale) contentMode:PHImageContentModeAspectFill options:imageRequestOptions resultHandler:^(UIImage *result, NSDictionary *info) {
          if (completion) {
              completion(result, info);
          }
    }];
}

- (NSInteger)requestPreviewImageWithCompletion:(void (^)(UIImage *result, NSDictionary<NSString *, id> *info))completion withProgressHandler:(PHAssetImageProgressHandler)phProgressHandler {
    PHImageRequestOptions *imageRequestOptions = [[PHImageRequestOptions alloc] init];
    imageRequestOptions.networkAccessAllowed = YES; // 允许访问网络
    imageRequestOptions.progressHandler = phProgressHandler;
    return [[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestImageForAsset:_phAsset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeAspectFill options:imageRequestOptions resultHandler:^(UIImage *result, NSDictionary *info) {
        if (completion) {
            completion(result, info);
        }
    }];
}

- (NSInteger)requestLivePhotoWithCompletion:(void (^)(PHLivePhoto *livePhoto, NSDictionary<NSString *, id> *info))completion withProgressHandler:(PHAssetImageProgressHandler)phProgressHandler {
    if ([[PHCachingImageManager class] instancesRespondToSelector:@selector(requestLivePhotoForAsset:targetSize:contentMode:options:resultHandler:)]) {
        PHLivePhotoRequestOptions *livePhotoRequestOptions = [[PHLivePhotoRequestOptions alloc] init];
        livePhotoRequestOptions.networkAccessAllowed = YES; // 允许访问网络
        livePhotoRequestOptions.progressHandler = phProgressHandler;
        return [[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestLivePhotoForAsset:_phAsset targetSize:CGSizeMake(SCREEN_WIDTH, SCREEN_HEIGHT) contentMode:PHImageContentModeDefault options:livePhotoRequestOptions resultHandler:^(PHLivePhoto * _Nullable livePhoto, NSDictionary * _Nullable info) {
            if (completion) {
                completion(livePhoto, info);
            }
        }];
    } else {
        if (completion) {
            completion(nil, nil);
        }
        return 0;
    }
}

- (NSInteger)requestPlayerItemWithCompletion:(void (^)(AVPlayerItem *playerItem, NSDictionary<NSString *, id> *info))completion withProgressHandler:(PHAssetVideoProgressHandler)phProgressHandler {
    if ([[PHCachingImageManager class] instancesRespondToSelector:@selector(requestPlayerItemForVideo:options:resultHandler:)]) {
        PHVideoRequestOptions *videoRequestOptions = [[PHVideoRequestOptions alloc] init];
        videoRequestOptions.networkAccessAllowed = YES; // 允许访问网络
        videoRequestOptions.progressHandler = phProgressHandler;
        return [[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestPlayerItemForVideo:_phAsset options:videoRequestOptions resultHandler:^(AVPlayerItem * _Nullable playerItem, NSDictionary * _Nullable info) {
            if (completion) {
                completion(playerItem, info);
            }
        }];
    } else {
        if (completion) {
            completion(nil, nil);
        }
        return 0;
    }
}

- (void)requestImageData:(void (^)(NSData *imageData, NSDictionary<NSString *, id> *info, BOOL isGIF, BOOL isHEIC))completion {
    if (self.assetType != QMUIAssetTypeImage) {
        if (completion) {
            completion(nil, nil, NO, NO);
        }
        return;
    }
    __weak __typeof(self)weakSelf = self;
    if (!self.phAssetInfo) {
        // PHAsset 的 UIImageOrientation 需要调用过 requestImageDataForAsset 才能获取
        [self requestPhAssetInfo:^(NSDictionary *phAssetInfo) {
            __strong __typeof(weakSelf)strongSelf = weakSelf;
            strongSelf.phAssetInfo = phAssetInfo;
            if (completion) {
                NSString *dataUTI = phAssetInfo[kAssetInfoDataUTI];
                BOOL isGIF = self.assetSubType == QMUIAssetSubTypeGIF;
                BOOL isHEIC = [dataUTI isEqualToString:@"public.heic"];
                NSDictionary<NSString *, id> *originInfo = phAssetInfo[kAssetInfoOriginInfo];
                completion(phAssetInfo[kAssetInfoImageData], originInfo, isGIF, isHEIC);
            }
        }];
    } else {
        if (completion) {
            NSString *dataUTI = self.phAssetInfo[kAssetInfoDataUTI];
            BOOL isGIF = self.assetSubType == QMUIAssetSubTypeGIF;
            BOOL isHEIC = [@"public.heic" isEqualToString:dataUTI];
            NSDictionary<NSString *, id> *originInfo = self.phAssetInfo[kAssetInfoOriginInfo];
            completion(self.phAssetInfo[kAssetInfoImageData], originInfo, isGIF, isHEIC);
        }
    }
}

- (UIImageOrientation)imageOrientation {
    UIImageOrientation orientation;
    if (self.assetType == QMUIAssetTypeImage) {
        if (!self.phAssetInfo) {
            // PHAsset 的 UIImageOrientation 需要调用过 requestImageDataForAsset 才能获取
            __weak __typeof(self)weakSelf = self;
            [self requestImagePhAssetInfo:^(NSDictionary *phAssetInfo) {
                __strong __typeof(weakSelf)strongSelf = weakSelf;
                strongSelf.phAssetInfo = phAssetInfo;
            } synchronous:YES];
        }
        // 从 PhAssetInfo 中获取 UIImageOrientation 对应的字段
        orientation = (UIImageOrientation)[self.phAssetInfo[kAssetInfoOrientation] integerValue];
    } else {
        orientation = UIImageOrientationUp;
    }
    return orientation;
}

- (NSString *)identifier {
    return _phAsset.localIdentifier;
}

- (void)requestPhAssetInfo:(void (^)(NSDictionary *))completion {
    if (!_phAsset) {
        if (completion) {
            completion(nil);
        }
        return;
    }
    if (self.assetType == QMUIAssetTypeVideo) {
        PHVideoRequestOptions *videoRequestOptions = [[PHVideoRequestOptions alloc] init];
        videoRequestOptions.networkAccessAllowed = YES;
        [[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestAVAssetForVideo:_phAsset options:videoRequestOptions resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
            if ([asset isKindOfClass:[AVURLAsset class]]) {
                NSMutableDictionary *tempInfo = [[NSMutableDictionary alloc] init];
                if (info) {
                    [tempInfo setObject:info forKey:kAssetInfoOriginInfo];
                }
                AVURLAsset *urlAsset = (AVURLAsset*)asset;
                NSNumber *size;
                [urlAsset.URL getResourceValue:&size forKey:NSURLFileSizeKey error:nil];
                [tempInfo setObject:size forKey:kAssetInfoSize];
                if (completion) {
                    completion(tempInfo);
                }
            }
        }];
    } else {
        [self requestImagePhAssetInfo:^(NSDictionary *phAssetInfo) {
            if (completion) {
                completion(phAssetInfo);
            }
        } synchronous:NO];
    }
}

- (void)requestImagePhAssetInfo:(void (^)(NSDictionary *))completion synchronous:(BOOL)synchronous {
    PHImageRequestOptions *imageRequestOptions = [[PHImageRequestOptions alloc] init];
    imageRequestOptions.synchronous = synchronous;
    imageRequestOptions.networkAccessAllowed = YES;
    [[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestImageDataForAsset:_phAsset options:imageRequestOptions resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
        if (info) {
            NSMutableDictionary *tempInfo = [[NSMutableDictionary alloc] init];
            if (imageData) {
                [tempInfo setObject:imageData forKey:kAssetInfoImageData];
                [tempInfo setObject:@(imageData.length) forKey:kAssetInfoSize];
            }
            [tempInfo setObject:info forKey:kAssetInfoOriginInfo];
            if (dataUTI) {
                [tempInfo setObject:dataUTI forKey:kAssetInfoDataUTI];
            }
            [tempInfo setObject:@(orientation) forKey:kAssetInfoOrientation];
            if (completion) {
                completion(tempInfo);
            }
        }
    }];
}

- (void)setDownloadProgress:(double)downloadProgress {
    _downloadProgress = downloadProgress;
    _downloadStatus = QMUIAssetDownloadStatusDownloading;
}

- (void)updateDownloadStatusWithDownloadResult:(BOOL)succeed {
    _downloadStatus = succeed ? QMUIAssetDownloadStatusSucceed : QMUIAssetDownloadStatusFailed;
}

- (void)assetSize:(void (^)(long long size))completion {
    if (!self.phAssetInfo) {
        // PHAsset 的 UIImageOrientation 需要调用过 requestImageDataForAsset 才能获取
        __weak __typeof(self)weakSelf = self;
        [self requestPhAssetInfo:^(NSDictionary *phAssetInfo) {
            __strong __typeof(weakSelf)strongSelf = weakSelf;
            strongSelf.phAssetInfo = phAssetInfo;
            if (completion) {
                /**
                 *  这里不在主线程执行,若用户在该 block 中操作 UI 时会产生一些问题,
                 *  为了避免这种情况,这里该 block 主动放到主线程执行。
                 */
                dispatch_async(dispatch_get_main_queue(), ^{
                    completion([phAssetInfo[kAssetInfoSize] longLongValue]);
                });
            }
        }];
    } else {
        if (completion) {
            completion([self.phAssetInfo[kAssetInfoSize] longLongValue]);
        }
    }
}

- (NSTimeInterval)duration {
    if (self.assetType != QMUIAssetTypeVideo) {
        return 0;
    }
    return _phAsset.duration;
}

- (BOOL)isEqual:(id)object {
    if (!object) return NO;
    if (self == object) return YES;
    if (![object isKindOfClass:[self class]]) return NO;
    return [self.identifier isEqualToString:((QMUIAsset *)object).identifier];
}

@end


================================================
FILE: QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetsGroup.h
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

//
//  QMUIAssetsGroup.h
//  qmui
//
//  Created by QMUI Team on 15/6/30.
//

#import <UIKit/UIKit.h>
#import <Photos/PHAsset.h>
#import <Photos/PHFetchOptions.h>
#import <Photos/PHCollection.h>
#import <Photos/PHFetchResult.h>
#import <Photos/PHImageManager.h>

@class QMUIAsset;

/// 相册展示内容的类型
typedef NS_ENUM(NSUInteger, QMUIAlbumContentType) {
    QMUIAlbumContentTypeAll,                                  // 展示所有资源
    QMUIAlbumContentTypeOnlyPhoto,                            // 只展示照片
    QMUIAlbumContentTypeOnlyVideo,                            // 只展示视频
    QMUIAlbumContentTypeOnlyAudio                             // 只展示音频
};

/// 相册展示内容按日期排序的方式
typedef NS_ENUM(NSUInteger, QMUIAlbumSortType) {
    QMUIAlbumSortTypePositive,  // 日期最新的内容排在后面
    QMUIAlbumSortTypeReverse  // 日期最新的内容排在前面
};


@interface QMUIAssetsGroup : NSObject

- (instancetype)initWithPHCollection:(PHAssetCollection *)phAssetCollection;

- (instancetype)initWithPHCollection:(PHAssetCollection *)phAssetCollection fetchAssetsOptions:(PHFetchOptions *)pHFetchOptions;

/// 仅能通过 initWithPHCollection 和 initWithPHCollection:fetchAssetsOption 方法修改 phAssetCollection 的值
@property(nonatomic, strong, readonly) PHAssetCollection *phAssetCollection;

/// 仅能通过 initWithPHCollection 和 initWithPHCollection:fetchAssetsOption 方法修改 phAssetCollection 后,产生一个对应的 PHAssetsFetchResults 保存到 phFetchResult 中
@property(nonatomic, strong, readonly) PHFetchResult *phFetchResult;

/// 相册的名称
- (NSString *)name;

/// 相册内的资源数量,包括视频、图片、音频(如果支持)这些类型的所有资源
- (NSInteger)numberOfAssets;

/**
 *  相册的缩略图,即系统接口中的相册海报(Poster Image)
 *
 *  @return 相册的缩略图
 */
- (UIImage *)posterImageWithSize:(CGSize)size;

/**
 *  枚举相册内所有的资源
 *
 *  @param albumSortType    相册内资源的排序方式,可以选择日期最新的排在最前面,也可以选择日期最新的排在最后面
 *  @param enumerationBlock 枚举相册内资源时调用的 block,参数 result 表示每次枚举时对应的资源。
 *                          枚举所有资源结束后,enumerationBlock 会被再调用一次,这时 result 的值为 nil。
 *                          可以以此作为判断枚举结束的标记
 */
- (void)enumerateAssetsWithOptions:(QMUIAlbumSortType)albumSortType usingBlock:(void (^)(QMUIAsset *resultAsset))enumerationBlock;

/**
 *  枚举相册内所有的资源,相册内资源按日期最新的排在最后面
 *
 *  @param enumerationBlock 枚举相册内资源时调用的 block,参数 result 表示每次枚举时对应的资源。
 *                          枚举所有资源结束后,enumerationBlock 会被再调用一次,这时 result 的值为 nil。
 *                          可以以此作为判断枚举结束的标记
 */
- (void)enumerateAssetsUsingBlock:(void (^)(QMUIAsset *result))enumerationBlock;

@end


================================================
FILE: QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetsGroup.m
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

//
//  QMUIAssetsGroup.m
//  qmui
//
//  Created by QMUI Team on 15/6/30.
//

#import "QMUIAssetsGroup.h"
#import "QMUICore.h"
#import "QMUIAsset.h"
#import "QMUIAssetsManager.h"

@interface QMUIAssetsGroup()

@property(nonatomic, strong, readwrite) PHAssetCollection *phAssetCollection;
@property(nonatomic, strong, readwrite) PHFetchResult *phFetchResult;

@end

@implementation QMUIAssetsGroup

- (instancetype)initWithPHCollection:(PHAssetCollection *)phAssetCollection fetchAssetsOptions:(PHFetchOptions *)pHFetchOptions {
    self = [super init];
    if (self) {
        self.phFetchResult = [PHAsset fetchAssetsInAssetCollection:phAssetCollection options:pHFetchOptions];
        self.phAssetCollection = phAssetCollection;
    }
    return self;
}

- (instancetype)initWithPHCollection:(PHAssetCollection *)phAssetCollection {
    return [self initWithPHCollection:phAssetCollection fetchAssetsOptions:nil];
}

- (NSInteger)numberOfAssets {
    return self.phFetchResult.count;
}

- (NSString *)name {
    NSString *resultName = self.phAssetCollection.localizedTitle;
    return NSLocalizedString(resultName, resultName);
}

- (UIImage *)posterImageWithSize:(CGSize)size {
    // 系统的隐藏相册不应该显示缩略图
    if (self.phAssetCollection.assetCollectionSubtype == PHAssetCollectionSubtypeSmartAlbumAllHidden) {
        return [QMUIHelper imageWithName:@"QMUI_hiddenAlbum"];
    }
    
    __block UIImage *resultImage;
    NSInteger count = self.phFetchResult.count;
    if (count > 0) {
        PHAsset *asset = self.phFetchResult[count - 1];
        PHImageRequestOptions *pHImageRequestOptions = [[PHImageRequestOptions alloc] init];
        pHImageRequestOptions.synchronous = YES; // 同步请求
        pHImageRequestOptions.resizeMode = PHImageRequestOptionsResizeModeExact;
        // targetSize 中对传入的 Size 进行处理,宽高各自乘以 ScreenScale,从而得到正确的图片
        [[[QMUIAssetsManager sharedInstance] phCachingImageManager] requestImageForAsset:asset targetSize:CGSizeMake(size.width * ScreenScale, size.height * ScreenScale) contentMode:PHImageContentModeAspectFill options:pHImageRequestOptions resultHandler:^(UIImage *result, NSDictionary *info) {
            resultImage = result;
        }];
    }
    return resultImage;
}

- (void)enumerateAssetsWithOptions:(QMUIAlbumSortType)albumSortType usingBlock:(void (^)(QMUIAsset *resultAsset))enumerationBlock {
    NSInteger resultCount = self.phFetchResult.count;
    if (albumSortType == QMUIAlbumSortTypeReverse) {
        for (NSInteger i = resultCount - 1; i >= 0; i--) {
            PHAsset *pHAsset = self.phFetchResult[i];
            QMUIAsset *asset = [[QMUIAsset alloc] initWithPHAsset:pHAsset];
            if (enumerationBlock) {
                enumerationBlock(asset);
            }
        }
    } else {
        for (NSInteger i = 0; i < resultCount; i++) {
            PHAsset *pHAsset = self.phFetchResult[i];
            QMUIAsset *asset = [[QMUIAsset alloc] initWithPHAsset:pHAsset];
            if (enumerationBlock) {
                enumerationBlock(asset);
            }
        }
    }
    /**
     *  For 循环遍历完毕,这时再调用一次 enumerationBlock,并传递 nil 作为实参,作为枚举资源结束的标记。
     */
    if (enumerationBlock) {
        enumerationBlock(nil);
    }
}

- (void)enumerateAssetsUsingBlock:(void (^)(QMUIAsset *resultAsset))enumerationBlock {
    [self enumerateAssetsWithOptions:QMUIAlbumSortTypePositive usingBlock:enumerationBlock];
}

@end


================================================
FILE: QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetsManager.h
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

//
//  QMUIAssetsManager.h
//  qmui
//
//  Created by QMUI Team on 15/6/9.
//

#import <UIKit/UIKit.h>
#import <Photos/PHPhotoLibrary.h>
#import <Photos/PHCollection.h>
#import <Photos/PHFetchResult.h>
#import <Photos/PHAssetChangeRequest.h>
#import <Photos/PHAssetCollectionChangeRequest.h>
#import <Photos/PHFetchOptions.h>
#import <Photos/PHImageManager.h>
#import "QMUIAssetsGroup.h"

@class PHCachingImageManager;
@class QMUIAsset;

/// Asset 授权的状态
typedef NS_ENUM(NSUInteger, QMUIAssetAuthorizationStatus) {
    QMUIAssetAuthorizationStatusNotDetermined,      // 还不确定有没有授权
    QMUIAssetAuthorizationStatusAuthorized,         // 已经授权
    QMUIAssetAuthorizationStatusNotAuthorized       // 手动禁止了授权
};

typedef void (^QMUIWriteAssetCompletionBlock)(QMUIAsset *asset, NSError *error);


/// 保存图片到指定相册(传入 UIImage)
extern void QMUIImageWriteToSavedPhotosAlbumWithAlbumAssetsGroup(UIImage *image, QMUIAssetsGroup *albumAssetsGroup, QMUIWriteAssetCompletionBlock completionBlock);

/// 保存图片到指定相册(传入图片路径)
extern void QMUISaveImageAtPathToSavedPhotosAlbumWithAlbumAssetsGroup(NSString *imagePath, QMUIAssetsGroup *albumAssetsGroup, QMUIWriteAssetCompletionBlock completionBlock);

/// 保存视频到指定相册
extern void QMUISaveVideoAtPathToSavedPhotosAlbumWithAlbumAssetsGroup(NSString *videoPath, QMUIAssetsGroup *albumAssetsGroup, QMUIWriteAssetCompletionBlock completionBlock);

/**
 *  构建 QMUIAssetsManager 这个对象并提供单例的调用方式主要出于下面两点考虑:
 *  1. 保存照片/视频的方法较为复杂,为了方便封装系统接口,同时灵活地扩展功能,需要有一个独立对象去管理这些方法。
 *  2. 使用 PhotoKit 获取图片,基本都需要一个 PHCachingImageManager 的实例,为了减少消耗,
 *     QMUIAssetsManager 单例内部也构建了一个 PHCachingImageManager,并且暴露给外面,方便获取
 *     PHCachingImageManager 的实例。
 */
@interface QMUIAssetsManager : NSObject

/// 获取 QMUIAssetsManager 的单例
+ (instancetype)sharedInstance;

/// 获取当前应用的“照片”访问授权状态
+ (QMUIAssetAuthorizationStatus)authorizationStatus;

/**
 *  调起系统询问是否授权访问“照片”的 UIAlertView
 *  @param handler 授权结束后调用的 block,默认不在主线程上执行,如果需要在 block 中修改 UI,记得 dispatch 到 mainqueue
 */
+ (void)requestAuthorization:(void(^)(QMUIAssetAuthorizationStatus status))handler;

/**
 *  获取所有的相册,包括个人收藏,最近添加,自拍这类“智能相册”
 *
 *  @param contentType               相册的内容类型,设定了内容类型后,所获取的相册中只包含对应类型的资源
 *  @param showEmptyAlbum            是否显示空相册(经过 contentType 过滤后仍为空的相册)
 *  @param showSmartAlbumIfSupported 是否显示"智能相册"
 *  @param enumerationBlock          参数 resultAssetsGroup 表示每次枚举时对应的相册。枚举所有相册结束后,enumerationBlock 会被再调用一次,
 *                                   这时 resultAssetsGroup 的值为 nil。可以以此作为判断枚举结束的标记。
 */
- (void)enumerateAllAlbumsWithAlbumContentType:(QMUIAlbumContentType)contentType showEmptyAlbum:(BOOL)showEmptyAlbum showSmartAlbumIfSupported:(BOOL)showSmartAlbumIfSupported usingBlock:(void (^)(QMUIAssetsGroup *resultAssetsGroup))enumerationBlock;

/// 获取所有相册,默认显示系统的“智能相册”,不显示空相册(经过 contentType 过滤后为空的相册)
- (void)enumerateAllAlbumsWithAlbumContentType:(QMUIAlbumContentType)contentType usingBlock:(void (^)(QMUIAssetsGroup *resultAssetsGroup))enumerationBlock;

/**
 *  保存图片或视频到指定的相册
 *
 *  @warning 无论用户保存到哪个自行创建的相册,系统都会在“相机胶卷”相册中同时保存这个图片。
 *           因为系统没有把图片和视频直接保存到指定相册的接口,都只能先保存到“相机胶卷”,从而生成了 Asset 对象,
 *           再把 Asset 对象添加到指定相册中,从而达到保存资源到指定相册的效果。
 *           即使调用 PhotoKit 保存图片或视频到指定相册的新接口也是如此,并且官方 PhotoKit SampleCode 中例子也是表现如此,
 *           因此这应该是一个合符官方预期的表现。
 *  @warning 无法通过该方法把图片保存到“智能相册”,“智能相册”只能由系统控制资源的增删。
 */
- (void)saveImageWithImageRef:(CGImageRef)imageRef albumAssetsGroup:(QMUIAssetsGroup *)albumAssetsGroup orientation:(UIImageOrientation)orientation completionBlock:(QMUIWriteAssetCompletionBlock)completionBlock;

- (void)saveImageWithImagePathURL:(NSURL *)imagePathURL albumAssetsGroup:(QMUIAssetsGroup *)albumAssetsGroup completionBlock:(QMUIWriteAssetCompletionBlock)completionBlock;

- (void)saveVideoWithVideoPathURL:(NSURL *)videoPathURL albumAssetsGroup:(QMUIAssetsGroup *)albumAssetsGroup completionBlock:(QMUIWriteAssetCompletionBlock)completionBlock;

/// 获取一个 PHCachingImageManager 的实例
- (PHCachingImageManager *)phCachingImageManager;

@end


@interface PHPhotoLibrary (QMUI)

/**
 *  根据 contentType 的值产生一个合适的 PHFetchOptions,并把内容以资源创建日期排序,创建日期较新的资源排在前面
 *
 *  @param contentType 相册的内容类型
 *
 *  @return 返回一个合适的 PHFetchOptions
 */
+ (PHFetchOptions *)createFetchOptionsWithAlbumContentType:(QMUIAlbumContentType)contentType;

/**
 *  获取所有相册
 *
 *  @param contentType    相册的内容类型,设定了内容类型后,所获取的相册中只包含对应类型的资源
 *  @param showEmptyAlbum 是否显示空相册(经过 contentType 过滤后仍为空的相册)
 *  @param showSmartAlbum 是否显示“智能相册”
 *
 *  @return 返回包含所有合适相册的数组
 */
+ (NSArray<PHAssetCollection *> *)fetchAllAlbumsWithAlbumContentType:(QMUIAlbumContentType)contentType showEmptyAlbum:(BOOL)showEmptyAlbum showSmartAlbum:(BOOL)showSmartAlbum;

/// 获取一个 PHAssetCollection 中创建日期最新的资源
+ (PHAsset *)fetchLatestAssetWithAssetCollection:(PHAssetCollection *)assetCollection;

/**
 *  保存图片或视频到指定的相册
 *
 *  @warning 无论用户保存到哪个自行创建的相册,系统都会在“相机胶卷”相册中同时保存这个图片。
 *           原因请参考 QMUIAssetsManager 对象的保存图片和视频方法的注释。
 *  @warning 无法通过该方法把图片保存到“智能相册”,“智能相册”只能由系统控制资源的增删。
 */
- (void)addImageToAlbum:(CGImageRef)imageRef albumAssetCollection:(PHAssetCollection *)albumAssetCollection orientation:(UIImageOrientation)orientation completionHandler:(void(^)(BOOL success, NSDate *creationDate, NSError *error))completionHandler;

- (void)addImageToAlbum:(NSURL *)imagePathURL albumAssetCollection:(PHAssetCollection *)albumAssetCollection completionHandler:(void(^)(BOOL success, NSDate *creationDate, NSError *error))completionHandler;

- (void)addVideoToAlbum:(NSURL *)videoPathURL albumAssetCollection:(PHAssetCollection *)albumAssetCollection completionHandler:(void(^)(BOOL success, NSDate *creationDate, NSError *error))completionHandler;

@end


================================================
FILE: QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetsManager.m
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

//
//  QMUIAssetsManager.m
//  qmui
//
//  Created by QMUI Team on 15/6/9.
//

#import "QMUIAssetsManager.h"
#import "QMUICore.h"
#import "QMUIAsset.h"
#import "QMUILog.h"

void QMUIImageWriteToSavedPhotosAlbumWithAlbumAssetsGroup(UIImage *image, QMUIAssetsGroup *albumAssetsGroup, QMUIWriteAssetCompletionBlock completionBlock) {
    [[QMUIAssetsManager sharedInstance] saveImageWithImageRef:image.CGImage albumAssetsGroup:albumAssetsGroup orientation:image.imageOrientation completionBlock:completionBlock];
}

void QMUISaveImageAtPathToSavedPhotosAlbumWithAlbumAssetsGroup(NSString *imagePath, QMUIAssetsGroup *albumAssetsGroup, QMUIWriteAssetCompletionBlock completionBlock) {
    [[QMUIAssetsManager sharedInstance] saveImageWithImagePathURL:[NSURL fileURLWithPath:imagePath] albumAssetsGroup:albumAssetsGroup completionBlock:completionBlock];
}

void QMUISaveVideoAtPathToSavedPhotosAlbumWithAlbumAssetsGroup(NSString *videoPath, QMUIAssetsGroup *albumAssetsGroup, QMUIWriteAssetCompletionBlock completionBlock) {
    [[QMUIAssetsManager sharedInstance] saveVideoWithVideoPathURL:[NSURL fileURLWithPath:videoPath] albumAssetsGroup:albumAssetsGroup completionBlock:completionBlock];
}



@implementation QMUIAssetsManager {
    PHCachingImageManager *_phCachingImageManager;
}

+ (QMUIAssetsManager *)sharedInstance {
    static dispatch_once_t onceToken;
    static QMUIAssetsManager *instance = nil;
    dispatch_once(&onceToken,^{
        instance = [[super allocWithZone:NULL] init];
    });
    return instance;
}

/**
 * 重写 +allocWithZone 方法,使得在给对象分配内存空间的时候,就指向同一份数据
 */

+ (id)allocWithZone:(struct _NSZone *)zone {
    return [self sharedInstance];
}

- (instancetype)init {
    if (self = [super init]) {
    }
    return self;
}

+ (QMUIAssetAuthorizationStatus)authorizationStatus {
    __block QMUIAssetAuthorizationStatus status;
    // 获取当前应用对照片的访问授权状态
    PHAuthorizationStatus authorizationStatus = [PHPhotoLibrary authorizationStatus];
    if (authorizationStatus == PHAuthorizationStatusRestricted || authorizationStatus == PHAuthorizationStatusDenied) {
        status = QMUIAssetAuthorizationStatusNotAuthorized;
    } else if (authorizationStatus == PHAuthorizationStatusNotDetermined) {
        status = QMUIAssetAuthorizationStatusNotDetermined;
    } else {
        status = QMUIAssetAuthorizationStatusAuthorized;
    }
    return status;
}

+ (void)requestAuthorization:(void(^)(QMUIAssetAuthorizationStatus status))handler {
    [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus phStatus) {
        QMUIAssetAuthorizationStatus status;
        if (phStatus == PHAuthorizationStatusRestricted || phStatus == PHAuthorizationStatusDenied) {
            status = QMUIAssetAuthorizationStatusNotAuthorized;
        } else if (phStatus == PHAuthorizationStatusNotDetermined) {
            status = QMUIAssetAuthorizationStatusNotDetermined;
        } else {
            status = QMUIAssetAuthorizationStatusAuthorized;
        }
        if (handler) {
            handler(status);
        }
    }];
}

- (void)enumerateAllAlbumsWithAlbumContentType:(QMUIAlbumContentType)contentType showEmptyAlbum:(BOOL)showEmptyAlbum showSmartAlbumIfSupported:(BOOL)showSmartAlbumIfSupported usingBlock:(void (^)(QMUIAssetsGroup *resultAssetsGroup))enumerationBlock {
    // 根据条件获取所有合适的相册,并保存到临时数组中
    NSArray<PHAssetCollection *> *tempAlbumsArray = [PHPhotoLibrary fetchAllAlbumsWithAlbumContentType:contentType showEmptyAlbum:showEmptyAlbum showSmartAlbum:showSmartAlbumIfSupported];
    
    // 创建一个 PHFetchOptions,用于 QMUIAssetsGroup 对资源的排序以及对内容类型进行控制
    PHFetchOptions *phFetchOptions = [PHPhotoLibrary createFetchOptionsWithAlbumContentType:contentType];
    
    // 遍历结果,生成对应的 QMUIAssetsGroup,并调用 enumerationBlock
    for (NSUInteger i = 0; i < tempAlbumsArray.count; i++) {
        PHAssetCollection *phAssetCollection = tempAlbumsArray[i];
        QMUIAssetsGroup *assetsGroup = [[QMUIAssetsGroup alloc] initWithPHCollection:phAssetCollection fetchAssetsOptions:phFetchOptions];
        if (enumerationBlock) {
            enumerationBlock(assetsGroup);
        }
    }
    
    /**
     *  所有结果遍历完毕,这时再调用一次 enumerationBlock,并传递 nil 作为实参,作为枚举相册结束的标记。
     */
    if (enumerationBlock) {
        enumerationBlock(nil);
    }
}

- (void)enumerateAllAlbumsWithAlbumContentType:(QMUIAlbumContentType)contentType usingBlock:(void (^)(QMUIAssetsGroup *resultAssetsGroup))enumerationBlock {
    [self enumerateAllAlbumsWithAlbumContentType:contentType showEmptyAlbum:NO showSmartAlbumIfSupported:YES usingBlock:enumerationBlock];
}

- (void)saveImageWithImageRef:(CGImageRef)imageRef albumAssetsGroup:(QMUIAssetsGroup *)albumAssetsGroup orientation:(UIImageOrientation)orientation completionBlock:(QMUIWriteAssetCompletionBlock)completionBlock {
    PHAssetCollection *albumPhAssetCollection = albumAssetsGroup.phAssetCollection;
    // 把图片加入到指定的相册对应的 PHAssetCollection
    [[PHPhotoLibrary sharedPhotoLibrary] addImageToAlbum:imageRef
                                    albumAssetCollection:albumPhAssetCollection
                                             orientation:orientation
                                       completionHandler:^(BOOL success, NSDate *creationDate, NSError *error) {
                                           if (success) {
                                               PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
                                               fetchOptions.predicate = [NSPredicate predicateWithFormat:@"creationDate = %@", creationDate];
                                               PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:albumPhAssetCollection options:fetchOptions];
                                               PHAsset *phAsset = fetchResult.lastObject;
                                               QMUIAsset *asset = [[QMUIAsset alloc] initWithPHAsset:phAsset];
                                               completionBlock(asset, error);
                                           } else {
                                               QMUILog(@"QMUIAssetLibrary", @"Get PHAsset of image error: %@", error);
                                               completionBlock(nil, error);
                                           }
                                       }];
}

- (void)saveImageWithImagePathURL:(NSURL *)imagePathURL albumAssetsGroup:(QMUIAssetsGroup *)albumAssetsGroup completionBlock:(QMUIWriteAssetCompletionBlock)completionBlock {
    PHAssetCollection *albumPhAssetCollection = albumAssetsGroup.phAssetCollection;
    // 把图片加入到指定的相册对应的 PHAssetCollection
    [[PHPhotoLibrary sharedPhotoLibrary] addImageToAlbum:imagePathURL
                                    albumAssetCollection:albumPhAssetCollection
                                       completionHandler:^(BOOL success, NSDate *creationDate, NSError *error) {
                                           if (success) {
                                               PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
                                               fetchOptions.predicate = [NSPredicate predicateWithFormat:@"creationDate = %@", creationDate];
                                               PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:albumPhAssetCollection options:fetchOptions];
                                               PHAsset *phAsset = fetchResult.lastObject;
                                               QMUIAsset *asset = [[QMUIAsset alloc] initWithPHAsset:phAsset];
                                               completionBlock(asset, error);
                                           } else {
                                               QMUILog(@"QMUIAssetLibrary", @"Get PHAsset of image error: %@", error);
                                               completionBlock(nil, error);
                                           }
                                       }];
}

- (void)saveVideoWithVideoPathURL:(NSURL *)videoPathURL albumAssetsGroup:(QMUIAssetsGroup *)albumAssetsGroup completionBlock:(QMUIWriteAssetCompletionBlock)completionBlock {
    PHAssetCollection *albumPhAssetCollection = albumAssetsGroup.phAssetCollection;
    // 把视频加入到指定的相册对应的 PHAssetCollection
    [[PHPhotoLibrary sharedPhotoLibrary] addVideoToAlbum:videoPathURL
                                    albumAssetCollection:albumPhAssetCollection
                                       completionHandler:^(BOOL success, NSDate *creationDate, NSError *error) {
                                           if (success) {
                                               PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
                                               fetchOptions.predicate = [NSPredicate predicateWithFormat:@"creationDate = %@", creationDate];
                                               PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:albumPhAssetCollection options:fetchOptions];
                                               PHAsset *phAsset = fetchResult.lastObject;
                                               QMUIAsset *asset = [[QMUIAsset alloc] initWithPHAsset:phAsset];
                                               completionBlock(asset, error);
                                           } else {
                                               QMUILog(@"QMUIAssetLibrary", @"Get PHAsset of video Error: %@", error);
                                               completionBlock(nil, error);
                                           }
                                       }];
}

- (PHCachingImageManager *)phCachingImageManager {
    if (!_phCachingImageManager) {
        _phCachingImageManager = [[PHCachingImageManager alloc] init];
    }
    return _phCachingImageManager;
}

@end


@implementation PHPhotoLibrary (QMUI)

+ (PHFetchOptions *)createFetchOptionsWithAlbumContentType:(QMUIAlbumContentType)contentType {
    PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
    // 根据输入的内容类型过滤相册内的资源
    switch (contentType) {
        case QMUIAlbumContentTypeOnlyPhoto:
            fetchOptions.predicate = [NSPredicate predicateWithFormat:@"mediaType = %i", PHAssetMediaTypeImage];
            break;
            
        case QMUIAlbumContentTypeOnlyVideo:
            fetchOptions.predicate = [NSPredicate predicateWithFormat:@"mediaType = %i",PHAssetMediaTypeVideo];
            break;
            
        case QMUIAlbumContentTypeOnlyAudio:
            fetchOptions.predicate = [NSPredicate predicateWithFormat:@"mediaType = %i",PHAssetMediaTypeAudio];
            break;
            
        default:
            break;
    }
    return fetchOptions;
}

+ (NSArray<PHAssetCollection *> *)fetchAllAlbumsWithAlbumContentType:(QMUIAlbumContentType)contentType showEmptyAlbum:(BOOL)showEmptyAlbum showSmartAlbum:(BOOL)showSmartAlbum {
    NSMutableArray<PHAssetCollection *> *tempAlbumsArray = [[NSMutableArray alloc] init];
    
    // 创建一个 PHFetchOptions,用于创建 QMUIAssetsGroup 对资源的排序和类型进行控制
    PHFetchOptions *fetchOptions = [PHPhotoLibrary createFetchOptionsWithAlbumContentType:contentType];
    
    PHFetchResult *fetchResult;
    if (showSmartAlbum) {
        // 允许显示系统的“智能相册”
        // 获取保存了所有“智能相册”的 PHFetchResult
        fetchResult = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAny options:nil];
    } else {
        // 不允许显示系统的智能相册,但由于在 PhotoKit 中,“相机胶卷”也属于“智能相册”,因此这里从“智能相册”中单独获取到“相机胶卷”
        fetchResult = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumUserLibrary options:nil];
    }
    // 循环遍历相册列表
    for (NSInteger i = 0; i < fetchResult.count; i++) {
        // 获取一个相册
        PHCollection *collection = fetchResult[i];
        if ([collection isKindOfClass:[PHAssetCollection class]]) {
            PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
            // 获取相册内的资源对应的 fetchResult,用于判断根据内容类型过滤后的资源数量是否大于 0,只有资源数量大于 0 的相册才会作为有效的相册显示
            PHFetchResult *currentFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:fetchOptions];
            if (currentFetchResult.count > 0 || showEmptyAlbum) {
                // 若相册不为空,或者允许显示空相册,则保存相册到结果数组
                // 判断如果是“相机胶卷”,则放到结果列表的第一位
                if (assetCollection.assetCollectionSubtype == PHAssetCollectionSubtypeSmartAlbumUserLibrary) {
                    [tempAlbumsArray insertObject:assetCollection atIndex:0];
                } else {
                    [tempAlbumsArray addObject:assetCollection];
                }
            }
        } else {
            NSAssert(NO, @"Fetch collection not PHCollection: %@", collection);
        }
    }
    
    // 获取所有用户自己建立的相册
    PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
    // 循环遍历用户自己建立的相册
    for (NSInteger i = 0; i < topLevelUserCollections.count; i++) {
        // 获取一个相册
        PHCollection *collection = topLevelUserCollections[i];
        if ([collection isKindOfClass:[PHAssetCollection class]]) {
            PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
            
            if (showEmptyAlbum) {
                // 允许显示空相册,直接保存相册到结果数组中
                [tempAlbumsArray addObject:assetCollection];
            } else {
                // 不允许显示空相册,需要判断当前相册是否为空
                PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:fetchOptions];
                // 获取相册内的资源对应的 fetchResult,用于判断根据内容类型过滤后的资源数量是否大于 0
                if (fetchResult.count > 0) {
                    [tempAlbumsArray addObject:assetCollection];
                }
            }
        }
    }
    
    // 获取从 macOS 设备同步过来的相册,同步过来的相册不允许删除照片,因此不会为空
    PHFetchResult *macCollections = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumSyncedAlbum options:nil];
    // 循环从 macOS 设备同步过来的相册
    for (NSInteger i = 0; i < macCollections.count; i++) {
        // 获取一个相册
        PHCollection *collection = macCollections[i];
        if ([collection isKindOfClass:[PHAssetCollection class]]) {
            PHAssetCollection *assetCollection = (PHAssetCollection *)collection;
            [tempAlbumsArray addObject:assetCollection];
        }
    }
    
    NSArray<PHAssetCollection *> *resultAlbumsArray = [tempAlbumsArray copy];
    return resultAlbumsArray;
}

+ (PHAsset *)fetchLatestAssetWithAssetCollection:(PHAssetCollection *)assetCollection {
    PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
    // 按时间的先后对 PHAssetCollection 内的资源进行排序,最新的资源排在数组最后面
    fetchOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]];
    PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:fetchOptions];
    // 获取 PHAssetCollection 内最后一个资源,即最新的资源
    PHAsset *latestAsset = fetchResult.lastObject;
    return latestAsset;
}

- (void)addImageToAlbum:(CGImageRef)imageRef albumAssetCollection:(PHAssetCollection *)albumAssetCollection orientation:(UIImageOrientation)orientation completionHandler:(void(^)(BOOL success, NSDate *creationDate, NSError *error))completionHandler {
    UIImage *targetImage = [UIImage imageWithCGImage:imageRef scale:ScreenScale orientation:orientation];
    [[PHPhotoLibrary sharedPhotoLibrary] addImageToAlbum:targetImage imagePathURL:nil albumAssetCollection:albumAssetCollection completionHandler:completionHandler];
}

- (void)addImageToAlbum:(NSURL *)imagePathURL albumAssetCollection:(PHAssetCollection *)albumAssetCollection completionHandler:(void (^)(BOOL success, NSDate *creationDate, NSError *error))completionHandler {
    [[PHPhotoLibrary sharedPhotoLibrary] addImageToAlbum:nil imagePathURL:imagePathURL albumAssetCollection:albumAssetCollection completionHandler:completionHandler];
}

- (void)addImageToAlbum:(UIImage *)image imagePathURL:(NSURL *)imagePathURL albumAssetCollection:(PHAssetCollection *)albumAssetCollection completionHandler:(void(^)(BOOL success, NSDate *creationDate, NSError *error))completionHandler {
    __block NSDate *creationDate = nil;
    [self performChanges:^{
        // 创建一个以图片生成新的 PHAsset,这时图片已经被添加到“相机胶卷”
        
        PHAssetChangeRequest *assetChangeRequest;
        if (image) {
            assetChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:image];
        } else if (imagePathURL) {
            assetChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:imagePathURL];
        } else {
            QMUILog(@"QMUIAssetLibrary", @"Creating asset with empty data");
            return;
        }
        assetChangeRequest.creationDate = [NSDate date];
        creationDate = assetChangeRequest.creationDate;
        
        if (albumAssetCollection.assetCollectionType == PHAssetCollectionTypeAlbum) {
            // 如果传入的相册类型为标准的相册(非“智能相册”和“时刻”),则把刚刚创建的 Asset 添加到传入的相册中。
            
            // 创建一个改变 PHAssetCollection 的请求,并指定相册对应的 PHAssetCollection
            PHAssetCollectionChangeRequest *assetCollectionChangeRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:albumAssetCollection];
            /**
             *  把 PHAsset 加入到对应的 PHAssetCollection 中,系统推荐的方法是调用 placeholderForCreatedAsset ,
             *  返回一个的 placeholder 来代替刚创建的 PHAsset 的引用,并把该引用加入到一个 PHAssetCollectionChangeRequest 中。
             */
            [assetCollectionChangeRequest addAssets:@[[assetChangeRequest placeholderForCreatedAsset]]];
        }
        
    } completionHandler:^(BOOL success, NSError *error) {
        if (!success) {
            QMUILog(@"QMUIAssetLibrary", @"Creating asset of image error : %@", error);
        }
        
        if (completionHandler) {
            /**
             *  performChanges:completionHandler 不在主线程执行,若用户在该 block 中操作 UI 时会产生一些问题,
             *  为了避免这种情况,这里该 block 主动放到主线程执行。
             */
            dispatch_async(dispatch_get_main_queue(), ^{
                BOOL creatingSuccess = success && creationDate; // 若创建时间为 nil,则说明 performChanges 中传入的资源为空,因此需要同时判断 performChanges 是否执行成功以及资源是否有创建时间。
                completionHandler(creatingSuccess, creationDate, error);
            });
        }
    }];
}


- (void)addVideoToAlbum:(NSURL *)videoPathURL albumAssetCollection:(PHAssetCollection *)albumAssetCollection completionHandler:(void(^)(BOOL success, NSDate *creationDate, NSError *error))completionHandler {
    __block NSDate *creationDate = nil;
    [self performChanges:^{
        // 创建一个以视频生成新的 PHAsset 的请求
        PHAssetChangeRequest *assetChangeRequest = [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:videoPathURL];
        assetChangeRequest.creationDate = [NSDate date];
        creationDate = assetChangeRequest.creationDate;
        
        if (albumAssetCollection.assetCollectionType == PHAssetCollectionTypeAlbum) {
            // 如果传入的相册类型为标准的相册(非“智能相册”和“时刻”),则把刚刚创建的 Asset 添加到传入的相册中。
            
            // 创建一个改变 PHAssetCollection 的请求,并指定相册对应的 PHAssetCollection
            PHAssetCollectionChangeRequest *assetCollectionChangeRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:albumAssetCollection];
            /**
             *  把 PHAsset 加入到对应的 PHAssetCollection 中,系统推荐的方法是调用 placeholderForCreatedAsset ,
             *  返回一个的 placeholder 来代替刚创建的 PHAsset 的引用,并把该引用加入到一个 PHAssetCollectionChangeRequest 中。
             */
            [assetCollectionChangeRequest addAssets:@[[assetChangeRequest placeholderForCreatedAsset]]];
        }
        
    } completionHandler:^(BOOL success, NSError *error) {
        if (!success) {
            QMUILog(@"QMUIAssetLibrary", @"Creating asset of video error: %@", error);
        }
        
        if (completionHandler) {
            /**
             *  performChanges:completionHandler 不在主线程执行,若用户在该 block 中操作 UI 时会产生一些问题,
             *  为了避免这种情况,这里该 block 主动放到主线程执行。
             */
            dispatch_async(dispatch_get_main_queue(), ^{
                completionHandler(success, creationDate, error);
            });
        }
    }];
}

@end


================================================
FILE: QMUIKit/QMUIComponents/CAAnimation+QMUI.h
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

//
//  CAAnimation+QMUI.h
//  QMUIKit
//
//  Created by QMUI Team on 2018/7/31.
//

#import <QuartzCore/QuartzCore.h>

// 这个文件依赖了 QMUIMultipleDelegates,无法作为 UIKitExtensions 的一部分,所以放在 QMUIComponents 内

@interface CAAnimation (QMUI)

@property(nonatomic, copy) void (^qmui_animationDidStartBlock)(__kindof CAAnimation *aAnimation);
@property(nonatomic, copy) void (^qmui_animationDidStopBlock)(__kindof CAAnimation *aAnimation, BOOL finished);
@end


================================================
FILE: QMUIKit/QMUIComponents/CAAnimation+QMUI.m
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

//
//  CAAnimation+QMUI.m
//  QMUIKit
//
//  Created by QMUI Team on 2018/7/31.
//

#import "CAAnimation+QMUI.h"
#import "QMUICore.h"
#import "QMUIMultipleDelegates.h"

@interface _QMUICAAnimationDelegator : NSObject<CAAnimationDelegate>

@end

@implementation CAAnimation (QMUI)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        ExtendImplementationOfNonVoidMethodWithSingleArgument([CAAnimation class], @selector(copyWithZone:), NSZone *, id, ^id(CAAnimation *selfObject, NSZone *firstArgv, id originReturnValue) {
            CAAnimation *animation = (CAAnimation *)originReturnValue;
            animation.qmui_multipleDelegatesEnabled = selfObject.qmui_multipleDelegatesEnabled;
            animation.qmui_animationDidStartBlock = selfObject.qmui_animationDidStartBlock;
            animation.qmui_animationDidStopBlock = selfObject.qmui_animationDidStopBlock;
            return animation;
        });
    });
}

- (void)enabledDelegateBlocks {
    self.qmui_multipleDelegatesEnabled = YES;
    BOOL shouldSetDelegator = !self.delegate;
    if (!shouldSetDelegator && [self.delegate isKindOfClass:[QMUIMultipleDelegates class]]) {
        QMUIMultipleDelegates *delegates = (QMUIMultipleDelegates *)self.delegate;
        NSPointerArray *array = delegates.delegates;
        for (NSUInteger i = 0; i < array.count; i++) {
            if ([((NSObject *)[array pointerAtIndex:i]) isKindOfClass:[_QMUICAAnimationDelegator class]]) {
                shouldSetDelegator = NO;
                break;
            }
        }
    }
    if (shouldSetDelegator) {
        self.delegate = [[_QMUICAAnimationDelegator alloc] init];// delegate is a strong property, it can retain _QMUICAAnimationDelegator
    }
}

static char kAssociatedObjectKey_animationDidStartBlock;
- (void)setQmui_animationDidStartBlock:(void (^)(__kindof CAAnimation *))qmui_animationDidStartBlock {
    objc_setAssociatedObject(self, &kAssociatedObjectKey_animationDidStartBlock, qmui_animationDidStartBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
    if (qmui_animationDidStartBlock) {
        [self enabledDelegateBlocks];
    }
}

- (void (^)(__kindof CAAnimation *))qmui_animationDidStartBlock {
    return (void (^)(__kindof CAAnimation *))objc_getAssociatedObject(self, &kAssociatedObjectKey_animationDidStartBlock);
}

static char kAssociatedObjectKey_animationDidStopBlock;
- (void)setQmui_animationDidStopBlock:(void (^)(__kindof CAAnimation *, BOOL))qmui_animationDidStopBlock {
    objc_setAssociatedObject(self, &kAssociatedObjectKey_animationDidStopBlock, qmui_animationDidStopBlock, OBJC_ASSOCIATION_COPY_NONATOMIC);
    if (qmui_animationDidStopBlock) {
        [self enabledDelegateBlocks];
    }
}

- (void (^)(__kindof CAAnimation *, BOOL))qmui_animationDidStopBlock {
    return (void (^)(__kindof CAAnimation *, BOOL))objc_getAssociatedObject(self, &kAssociatedObjectKey_animationDidStopBlock);
}

@end

@implementation _QMUICAAnimationDelegator

- (void)animationDidStart:(CAAnimation *)anim {
    if (anim.qmui_animationDidStartBlock) {
        anim.qmui_animationDidStartBlock(anim);
    }
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    if (anim.qmui_animationDidStopBlock) {
        anim.qmui_animationDidStopBlock(anim, flag);
    }
}

@end


================================================
FILE: QMUIKit/QMUIComponents/CALayer+QMUIViewAnimation.h
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */
//
//  CALayer+QMUIViewAnimation.h
//  QMUIKit
//
//  Created by ziezheng on 2020/4/4.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface CALayer (QMUIViewAnimation)

/**
 开启了该属性的 CALayer 可在 +[UIView animateWithDuration:animations:]  执行动画,系统默认是不支持这种做法的。
 
 @code
 [UIView animateWithDuration:1 animations:^{
     layer.frame = xxx;
 } completion:nil];
 @endcode
 */
@property(nonatomic, assign) BOOL qmui_viewAnimationEnabled;

@end

NS_ASSUME_NONNULL_END


================================================
FILE: QMUIKit/QMUIComponents/CALayer+QMUIViewAnimation.m
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */
//
//  CALayer+QMUIViewAnimation.m
//  QMUIKit
//
//  Created by ziezheng on 2020/4/4.
//

#import "CALayer+QMUIViewAnimation.h"
#import "CALayer+QMUI.h"
#import "QMUICore.h"
#import "QMUIMultipleDelegates.h"


@interface _QMUICALayerDelegator : NSObject <CALayerDelegate>

@end

@implementation _QMUICALayerDelegator

+ (instancetype)sharedDelegator {
    static dispatch_once_t onceToken;
    static _QMUICALayerDelegator *instance = nil;
    dispatch_once(&onceToken,^{
        instance = [[super allocWithZone:NULL] init];
    });
    return instance;
}

+ (id)allocWithZone:(struct _NSZone *)zone {
    return [self sharedDelegator];
}

- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event {
    static UIView *standardView = nil;
    if (!standardView) standardView = UIView.new;
    // 被 +[UIView animateWithDuration:animations:] 包裹的代码可利用任意 UIView 的 actionForLayer:forKey: 来获得默认的 CAAction
    id<CAAction> action = [standardView actionForLayer:standardView.layer forKey:event];
    if (action == [NSNull null]) {
        // -[CALayer actionForKey:] 会先询问本代理,一旦代理返回了 NSNull, 则不会执行 self.actions 里隐式动画,为保持 CALayer 的原有逻辑,这里返回 nil,详见 -[CALayer actionForKey:] 的文档描述。
        return nil;
    } else {
        return action;
    }
}

@end

@implementation CALayer (QMUIViewAnimation)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        OverrideImplementation([CALayer class], @selector(addAnimation:forKey:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) {
            return ^(CALayer *selfObject, CAAnimation *animation, NSString *key) {
                if (selfObject.qmui_viewAnimationEnabled) {
                    BOOL isViewAnimtion = [animation isKindOfClass:CABasicAnimation.class] && [animation.delegate isKindOfClass:NSClassFromString(@"UIViewAnimationState")];
                    if (isViewAnimtion) {
                        // 这里需要清空 fromValue 和 toValue,后面会在 CAMediaTimingCopyRenderTiming 取到这个 animtion 的参数并设置到 CATransaction 中,让 Layer 改变属性时,运用上这些动画
                        ((CABasicAnimation *)animation).fromValue = nil;
                        ((CABasicAnimation *)animation).toValue = nil;
                        // 这个机制下的 toValue 已是最终值,这里 additive 要设置成 NO,否则会多叠加一次计算结果,导致动画出错。
                        ((CABasicAnimation *)animation).additive = NO;
                    }
                }
                void (*originSelectorIMP)(id, SEL, CAAnimation *, NSString *);
                originSelectorIMP = (void (*)(id, SEL, CAAnimation *, NSString *))originalIMPProvider();
                originSelectorIMP(selfObject, originCMD, animation, key);
                
            };
        });
    });
}


static char kAssociatedObjectKey_qmuiviewAnimationEnabled;
- (void)setQmui_viewAnimationEnabled:(BOOL)qmui_viewAnimationEnabled {
    QMUIAssert(!self.qmui_isRootLayerOfView, @"CALayer (QMUIViewAnimation)", @"UIView 本身的 Layer 无须开启 %s", __func__);
    objc_setAssociatedObject(self, &kAssociatedObjectKey_qmuiviewAnimationEnabled, @(qmui_viewAnimationEnabled), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    if (qmui_viewAnimationEnabled) {
        self.qmui_multipleDelegatesEnabled = YES;
        self.delegate = [_QMUICALayerDelegator sharedDelegator];
    } else {
        [self qmui_removeDelegate:[_QMUICALayerDelegator sharedDelegator]];
    }
}

- (BOOL)qmui_viewAnimationEnabled {
    return [((NSNumber *)objc_getAssociatedObject(self, &kAssociatedObjectKey_qmuiviewAnimationEnabled)) boolValue];
}

@end


================================================
FILE: QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIAlbumViewController.h
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

//
//  QMUIAlbumViewController.h
//  qmui
//
//  Created by QMUI Team on 15/5/3.
//

#import <UIKit/UIKit.h>
#import "QMUICommonTableViewController.h"
#import "QMUITableViewCell.h"
#import "QMUIAssetsGroup.h"

NS_ASSUME_NONNULL_BEGIN

@class QMUIImagePickerViewController;
@class QMUIAlbumViewController;
@class QMUITableViewCell;

@protocol QMUIAlbumViewControllerDelegate <NSObject>

@required
/// 点击相簿里某一行时,需要给一个 QMUIImagePickerViewController 对象用于展示九宫格图片列表
- (QMUIImagePickerViewController *)imagePickerViewControllerForAlbumViewController:(QMUIAlbumViewController *)albumViewController;

@optional
/**
 *  取消查看相册列表后被调用
 */
- (void)albumViewControllerDidCancel:(QMUIAlbumViewController *)albumViewController;

/**
 *  即将需要显示 Loading 时调用
 *
 *  @see shouldShowDefaultLoadingView
 */
- (void)albumViewControllerWillStartLoading:(QMUIAlbumViewController *)albumViewController;

/**
 *  即将需要隐藏 Loading 时调用
 *
 *  @see shouldShowDefaultLoadingView
 */
- (void)albumViewControllerWillFinishLoading:(QMUIAlbumViewController *)albumViewController;

@end


@interface QMUIAlbumTableViewCell : QMUITableViewCell

@property(nonatomic, assign) CGFloat albumImageSize UI_APPEARANCE_SELECTOR; // 相册缩略图的大小
@property(nonatomic, assign) CGFloat albumImageMarginLeft UI_APPEARANCE_SELECTOR; // 相册缩略图的 left,-1 表示自动保持与上下 margin 相等
@property(nonatomic, assign) UIEdgeInsets albumNameInsets UI_APPEARANCE_SELECTOR; // 相册名称的上下左右间距
@property(nullable, nonatomic, strong) UIFont *albumNameFont UI_APPEARANCE_SELECTOR; // 相册名的字体
@property(nullable, nonatomic, strong) UIColor *albumNameColor UI_APPEARANCE_SELECTOR; // 相册名的颜色
@property(nullable, nonatomic, strong) UIFont *albumAssetsNumberFont UI_APPEARANCE_SELECTOR; // 相册资源数量的字体
@property(nullable, nonatomic, strong) UIColor *albumAssetsNumberColor UI_APPEARANCE_SELECTOR; // 相册资源数量的颜色

@end

/**
 *  当前设备照片里的相簿列表,使用方式:
 *  1. 使用 init 初始化。
 *  2. 指定一个 albumViewControllerDelegate,并实现 @required 方法。
 *
 *  @warning 注意,iOS 访问相册需要得到授权,建议先询问用户授权,通过了再进行 QMUIAlbumViewController 的初始化工作。关于授权的代码,可参考 QMUI Demo 项目里的 [QDImagePickerExampleViewController authorizationPresentAlbumViewControllerWithTitle] 方法。
 *  @see [QMUIAssetsManager requestAuthorization:]
 */
@interface QMUIAlbumViewController : QMUICommonTableViewController

@property(nullable, nonatomic, weak) id<QMUIAlbumViewControllerDelegate> albumViewControllerDelegate;

/// 相册列表 cell 的高度,同时也是相册预览图的宽高,默认57
@property(nonatomic, assign) CGFloat albumTableViewCellHeight UI_APPEARANCE_SELECTOR;

/// 相册展示内容的类型,可以控制只展示照片、视频或音频的其中一种,也可以同时展示所有类型的资源,默认展示所有类型的资源。
@property(nonatomic, assign) QMUIAlbumContentType contentType;

@property(nullable, nonatomic, copy) NSString *tipTextWhenNoPhotosAuthorization;
@property(nullable, nonatomic, copy) NSString *tipTextWhenPhotosEmpty;

/**
 *  加载相册列表时会出现 loading,若需要自定义 loading 的形式,可将该属性置为 NO,默认为 YES。
 *  @see albumViewControllerWillStartLoading: & albumViewControllerWillFinishLoading:
 */
@property(nonatomic, assign) BOOL shouldShowDefaultLoadingView;

/// 在 QMUIAlbumViewController 被放到 UINavigationController 里之后,可通过调用这个方法,来尝试直接进入上一次选中的相册列表
- (void)pickLastAlbumGroupDirectlyIfCan;

@end


@interface QMUIAlbumViewController (UIAppearance)

+ (instancetype)appearance;

@end

NS_ASSUME_NONNULL_END


================================================
FILE: QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIAlbumViewController.m
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

//
//  QMUIAlbumViewController.m
//  qmui
//
//  Created by QMUI Team on 15/5/3.
//

#import "QMUIAlbumViewController.h"
#import "QMUICore.h"
#import "QMUINavigationButton.h"
#import "UIView+QMUI.h"
#import "QMUIAssetsManager.h"
#import "QMUIImagePickerViewController.h"
#import "QMUIImagePickerHelper.h"
#import "QMUIAppearance.h"
#import <Photos/PHPhotoLibrary.h>
#import <Photos/PHAsset.h>
#import <Photos/PHFetchOptions.h>
#import <Photos/PHCollection.h>
#import <Photos/PHFetchResult.h>

#pragma mark - QMUIAlbumTableViewCell

@implementation QMUIAlbumTableViewCell

+ (void)initialize {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [QMUIAlbumTableViewCell appearance].albumImageSize = 72;
        [QMUIAlbumTableViewCell appearance].albumImageMarginLeft = 16;
        [QMUIAlbumTableViewCell appearance].albumNameInsets = UIEdgeInsetsMake(0, 14, 0, 3);
        [QMUIAlbumTableViewCell appearance].albumNameFont = UIFontMake(17);
        [QMUIAlbumTableViewCell appearance].albumNameColor = TableViewCellTitleLabelColor;
        [QMUIAlbumTableViewCell appearance].albumAssetsNumberFont = UIFontMake(17);
        [QMUIAlbumTableViewCell appearance].albumAssetsNumberColor = TableViewCellTitleLabelColor;
    });
}

- (void)didInitializeWithStyle:(UITableViewCellStyle)style {
    [super didInitializeWithStyle:style];
    
    [self qmui_applyAppearance];
    
    self.imageView.contentMode = UIViewContentModeScaleAspectFill;
    self.imageView.clipsToBounds = YES;
    self.imageView.layer.borderWidth = PixelOne;
    self.imageView.layer.borderColor = UIColorMakeWithRGBA(0, 0, 0, .1).CGColor;
}

- (void)updateCellAppearanceWithIndexPath:(NSIndexPath *)indexPath {
    [super updateCellAppearanceWithIndexPath:indexPath];
    self.textLabel.font = self.albumNameFont;
    self.detailTextLabel.font = self.albumAssetsNumberFont;
}

- (void)layoutSubviews {
    [super layoutSubviews];
    
    CGFloat imageEdgeTop = CGFloatGetCenter(CGRectGetHeight(self.contentView.bounds), self.albumImageSize);
    CGFloat imageEdgeLeft = self.albumImageMarginLeft == -1 ? imageEdgeTop : self.albumImageMarginLeft;
    self.imageView.frame = CGRectMake(imageEdgeLeft, imageEdgeTop, self.albumImageSize, self.albumImageSize);
    
    self.textLabel.frame = CGRectSetXY(self.textLabel.frame, CGRectGetMaxX(self.imageView.frame) + self.albumNameInsets.left, [self.textLabel qmui_topWhenCenterInSuperview]);
    
    CGFloat textLabelMaxWidth = CGRectGetWidth(self.contentView.bounds) - CGRectGetMinX(self.textLabel.frame) - CGRectGetWidth(self.detailTextLabel.bounds) - self.albumNameInsets.right;
    if (CGRectGetWidth(self.textLabel.bounds) > textLabelMaxWidth) {
        self.textLabel.frame = CGRectSetWidth(self.textLabel.frame, textLabelMaxWidth);
    }
    
    self.detailTextLabel.frame = CGRectSetXY(self.detailTextLabel.frame, CGRectGetMaxX(self.textLabel.frame) + self.albumNameInsets.right, [self.detailTextLabel qmui_topWhenCenterInSuperview]);
}

- (void)setAlbumNameFont:(UIFont *)albumNameFont {
    _albumNameFont = albumNameFont;
    self.textLabel.font = albumNameFont;
}

- (void)setAlbumNameColor:(UIColor *)albumNameColor {
    _albumNameColor = albumNameColor;
    self.textLabel.textColor = albumNameColor;
}

- (void)setAlbumAssetsNumberFont:(UIFont *)albumAssetsNumberFont {
    _albumAssetsNumberFont = albumAssetsNumberFont;
    self.detailTextLabel.font = albumAssetsNumberFont;
}

- (void)setAlbumAssetsNumberColor:(UIColor *)albumAssetsNumberColor {
    _albumAssetsNumberColor = albumAssetsNumberColor;
    self.detailTextLabel.textColor = albumAssetsNumberColor;
}

@end


#pragma mark - QMUIAlbumViewController (UIAppearance)

@implementation QMUIAlbumViewController (UIAppearance)

+ (instancetype)appearance {
    return [QMUIAppearance appearanceForClass:self];
}

+ (void)initialize {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self initAppearance];
    });
}

+ (void)initAppearance {
    QMUIAlbumViewController.appearance.albumTableViewCellHeight = 88;
}

@end


#pragma mark - QMUIAlbumViewController

@interface QMUIAlbumViewController ()

@property(nonatomic, strong) NSMutableArray<QMUIAssetsGroup *> *albumsArray;
@property(nonatomic, strong) QMUIImagePickerViewController *imagePickerViewController;
@end

@implementation QMUIAlbumViewController

- (void)didInitialize {
    [super didInitialize];
    _shouldShowDefaultLoadingView = YES;
    [self qmui_applyAppearance];
}

- (void)setupNavigationItems {
    [super setupNavigationItems];
    if (!self.title) {
        self.title = @"照片";
    }
    self.navigationItem.rightBarButtonItem = [UIBarButtonItem qmui_itemWithTitle:@"取消" target:self action:@selector(handleCancelSelectAlbum:)];
}

- (void)initTableView {
    [super initTableView];
    self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    if ([QMUIAssetsManager authorizationStatus] == QMUIAssetAuthorizationStatusNotAuthorized) {
        // 如果没有获取访问授权,或者访问授权状态已经被明确禁止,则显示提示语,引导用户开启授权
        NSString *tipString = self.tipTextWhenNoPhotosAuthorization;
        if (!tipString) {
            NSDictionary *mainInfoDictionary = [[NSBundle mainBundle] infoDictionary];
            NSString *appName = [mainInfoDictionary objectForKey:@"CFBundleDisplayName"];
            if (!appName) {
                appName = [mainInfoDictionary objectForKey:(NSString *)kCFBundleNameKey];
            }
            tipString = [NSString stringWithFormat:@"请在设备的\"设置-隐私-照片\"选项中,允许%@访问你的手机相册", appName];
        }
        [self showEmptyViewWithText:tipString detailText:nil buttonTitle:nil buttonAction:nil];
    } else {
        self.albumsArray = [[NSMutableArray alloc] init];
        // 获取相册列表较为耗时,交给子线程去处理,因此这里需要显示 Loading
        if ([self.albumViewControllerDelegate respondsToSelector:@selector(albumViewControllerWillStartLoading:)]) {
            [self.albumViewControllerDelegate albumViewControllerWillStartLoading:self];
        }
        if (self.shouldShowDefaultLoadingView) {
            [self showEmptyViewWithLoading];
        }
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [[QMUIAssetsManager sharedInstance] enumerateAllAlbumsWithAlbumContentType:self.contentType usingBlock:^(QMUIAssetsGroup *resultAssetsGroup) {
                if (resultAssetsGroup) {
                    [self.albumsArray addObject:resultAssetsGroup];
                } else {
                    // 意味着遍历完所有的相簿了
                    [self sortAlbumArray];
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [self refreshAlbumAndShowEmptyTipIfNeed];
                    });
                }
            }];
        });
    }
}

- (void)sortAlbumArray {
    // 把隐藏相册排序强制放到最后
    __block QMUIAssetsGroup *hiddenGroup = nil;
    [self.albumsArray enumerateObjectsUsingBlock:^(QMUIAssetsGroup * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if (obj.phAssetCollection.assetCollectionSubtype == PHAssetCollectionSubtypeSmartAlbumAllHidden) {
            hiddenGroup = obj;
            *stop = YES;
        }
    }];
    if (hiddenGroup) {
        [self.albumsArray removeObject:hiddenGroup];
        [self.albumsArray addObject:hiddenGroup];
    }
}

- (void)refreshAlbumAndShowEmptyTipIfNeed {
    if ([self.albumsArray count] > 0) {
        if ([self.albumViewControllerDelegate respondsToSelector:@selector(albumViewControllerWillFinishLoading:)]) {
            [self.albumViewControllerDelegate albumViewControllerWillFinishLoading:self];
        }
        if (self.shouldShowDefaultLoadingView) {
            [self hideEmptyView];
        }
        [self.tableView reloadData];
    } else {
        NSString *tipString = self.tipTextWhenPhotosEmpty ? : @"空照片";
        [self showEmptyViewWithText:tipString detailText:nil buttonTitle:nil buttonAction:nil];
    }
}

- (void)pickAlbumsGroup:(QMUIAssetsGroup *)assetsGroup animated:(BOOL)animated {
    if (!assetsGroup) return;
    
    if (!self.imagePickerViewController) {
        self.imagePickerViewController = [self.albumViewControllerDelegate imagePickerViewControllerForAlbumViewController:self];
    }
    QMUIAssert(!!self.imagePickerViewController, NSStringFromClass(self.class), NSStringFromClass(self.class), @"self.%@ 必须实现 %@ 并返回一个 %@ 对象", NSStringFromSelector(@selector(albumViewControllerDelegate)), NSStringFromSelector(@selector(imagePickerViewControllerForAlbumViewController:)), NSStringFromClass([QMUIImagePickerViewController class]));
    
    [self.imagePickerViewController refreshWithAssetsGroup:assetsGroup];
    self.imagePickerViewController.title = [assetsGroup name];
    [self.navigationController pushViewController:self.imagePickerViewController animated:animated];
}

- (void)pickLastAlbumGroupDirectlyIfCan {
    QMUIAssetsGroup *assetsGroup = [QMUIImagePickerHelper assetsGroupOfLastPickerAlbumWithUserIdentify:nil];
    [self pickAlbumsGroup:assetsGroup animated:NO];
}

#pragma mark - <UITableViewDelegate,UITableViewDataSource>

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [self.albumsArray count];
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return self.albumTableViewCellHeight;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *kCellIdentifer = @"cell";
    QMUIAlbumTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifer];
    if (!cell) {
        cell = [[QMUIAlbumTableViewCell alloc] initForTableView:tableView withStyle:UITableViewCellStyleSubtitle reuseIdentifier:kCellIdentifer];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }
    QMUIAssetsGroup *assetsGroup = self.albumsArray[indexPath.row];
    cell.imageView.image = [assetsGroup posterImageWithSize:CGSizeMake(self.albumTableViewCellHeight, self.albumTableViewCellHeight)];
    cell.textLabel.text = [assetsGroup name];
    cell.detailTextLabel.text = [NSString stringWithFormat:@"· %@", @(assetsGroup.numberOfAssets)];
    [cell updateCellAppearanceWithIndexPath:indexPath];
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [self pickAlbumsGroup:self.albumsArray[indexPath.row] animated:YES];
}

- (void)handleCancelSelectAlbum:(id)sender {
    [self dismissViewControllerAnimated:YES completion:^(void) {
        if (self.albumViewControllerDelegate && [self.albumViewControllerDelegate respondsToSelector:@selector(albumViewControllerDidCancel:)]) {
            [self.albumViewControllerDelegate albumViewControllerDidCancel:self]; 
        }
        [self.imagePickerViewController.selectedImageAssetArray removeAllObjects];
    }];
}

@end


================================================
FILE: QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerCollectionViewCell.h
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

//
//  QMUIImagePickerCollectionViewCell.h
//  qmui
//
//  Created by QMUI Team on 16/8/29.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "QMUIAsset.h"

@class QMUIButton;

/**
 *  图片选择空间里的九宫格 cell,支持显示 checkbox、饼状进度条及重试按钮(iCloud 图片需要)
 */
@interface QMUIImagePickerCollectionViewCell : UICollectionViewCell

/// 收藏的资源的心形图片
@property(nonatomic, strong) UIImage *favoriteImage UI_APPEARANCE_SELECTOR;

/// 收藏的资源的心形图片的上下左右间距,相对于 cell 左下角零点而言,也即如果 left 越大则越往右,bottom 越大则越往上,另外 top 会影响底部遮罩的高度
@property(nonatomic, assign) UIEdgeInsets favoriteImageMargins UI_APPEARANCE_SELECTOR;

/// checkbox 未被选中时显示的图片
@property(nonatomic, strong) UIImage *checkboxImage UI_APPEARANCE_SELECTOR;

/// checkbox 被选中时显示的图片
@property(nonatomic, strong) UIImage *checkboxCheckedImage UI_APPEARANCE_SELECTOR;

/// checkbox 的 margin,定位从每个 cell(即每张图片)的最右边开始计算
@property(nonatomic, assign) UIEdgeInsets checkboxButtonMargins UI_APPEARANCE_SELECTOR;

/// videoDurationLabel 的字号
@property(nonatomic, strong) UIFont *videoDurationLabelFont UI_APPEARANCE_SELECTOR;

/// videoDurationLabel 的字体颜色
@property(nonatomic, strong) UIColor *videoDurationLabelTextColor UI_APPEARANCE_SELECTOR;

/// 视频时长文字的间距,相对于 cell 右下角而言,也即如果 right 越大则越往左,bottom 越大则越往上,另外 top 会影响底部遮罩的高度
@property(nonatomic, assign) UIEdgeInsets videoDurationLabelMargins UI_APPEARANCE_SELECTOR;

@property(nonatomic, strong, readonly) UIImageView *contentImageView;
@property(nonatomic, strong, readonly) UIImageView *favoriteImageView;
@property(nonatomic, strong, readonly) QMUIButton *checkboxButton;
@property(nonatomic, strong, readonly) UILabel *videoDurationLabel;
@property(nonatomic, strong, readonly) CAGradientLayer *bottomShadowLayer;// 当出现收藏或者视频时长文字时就会显示遮罩,遮罩高度为 favoriteImage 和 videoDurationLabel 中最高者的高度

@property(nonatomic, assign, getter=isSelectable) BOOL selectable;
@property(nonatomic, assign, getter=isChecked) BOOL checked;
@property(nonatomic, assign) QMUIAssetDownloadStatus downloadStatus; // Cell 中对应资源的下载状态,这个值的变动会相应地调整 UI 表现
@property(nonatomic, copy) NSString *assetIdentifier;// 当前这个 cell 正在展示的 QMUIAsset 的 identifier

- (void)renderWithAsset:(QMUIAsset *)asset referenceSize:(CGSize)referenceSize;

@end


================================================
FILE: QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerCollectionViewCell.m
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

//
//  QMUIImagePickerCollectionViewCell.m
//  qmui
//
//  Created by QMUI Team on 16/8/29.
//

#import "QMUIImagePickerCollectionViewCell.h"
#import "QMUICore.h"
#import "QMUIImagePickerHelper.h"
#import "QMUIPieProgressView.h"
#import "UIControl+QMUI.h"
#import "UILabel+QMUI.h"
#import "CALayer+QMUI.h"
#import "QMUIButton.h"
#import "UIView+QMUI.h"
#import "NSString+QMUI.h"
#import "QMUIAppearance.h"

@interface QMUIImagePickerCollectionViewCell ()

@property(nonatomic, strong, readwrite) UIImageView *favoriteImageView;
@property(nonatomic, strong, readwrite) QMUIButton *checkboxButton;
@property(nonatomic, strong, readwrite) CAGradientLayer *bottomShadowLayer;

@end


@implementation QMUIImagePickerCollectionViewCell

@synthesize videoDurationLabel = _videoDurationLabel;

+ (void)initialize {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [QMUIImagePickerCollectionViewCell appearance].favoriteImage = [QMUIHelper imageWithName:@"QMUI_pickerImage_favorite"];
        [QMUIImagePickerCollectionViewCell appearance].favoriteImageMargins = UIEdgeInsetsMake(6, 6, 6, 6);
        [QMUIImagePickerCollectionViewCell appearance].checkboxImage = [QMUIHelper imageWithName:@"QMUI_pickerImage_checkbox"];
        [QMUIImagePickerCollectionViewCell appearance].checkboxCheckedImage = [QMUIHelper imageWithName:@"QMUI_pickerImage_checkbox_checked"];
        [QMUIImagePickerCollectionViewCell appearance].checkboxButtonMargins = UIEdgeInsetsMake(6, 6, 6, 6);
        [QMUIImagePickerCollectionViewCell appearance].videoDurationLabelFont = UIFontMake(12);
        [QMUIImagePickerCollectionViewCell appearance].videoDurationLabelTextColor = UIColorWhite;
        [QMUIImagePickerCollectionViewCell appearance].videoDurationLabelMargins = UIEdgeInsetsMake(5, 5, 5, 7);
    });
}

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self initImagePickerCollectionViewCellUI];
        [self qmui_applyAppearance];
    }
    return self;
}

- (void)initImagePickerCollectionViewCellUI {
    _contentImageView = [[UIImageView alloc] init];
    self.contentImageView.contentMode = UIViewContentModeScaleAspectFill;
    self.contentImageView.clipsToBounds = YES;
    [self.contentView addSubview:self.contentImageView];
    
    self.bottomShadowLayer = [CAGradientLayer layer];
    [self.bottomShadowLayer qmui_removeDefaultAnimations];
    self.bottomShadowLayer.colors = @[(id)UIColorMakeWithRGBA(0, 0, 0, 0).CGColor, (id)UIColorMakeWithRGBA(0, 0, 0, .6).CGColor];
    self.bottomShadowLayer.hidden = YES;
    [self.contentView.layer addSublayer:self.bottomShadowLayer];
    [self setNeedsLayout];
    
    self.favoriteImageView = [[UIImageView alloc] init];
    self.favoriteImageView.hidden = YES;
    [self.contentView addSubview:self.favoriteImageView];
    
    self.checkboxButton = [[QMUIButton alloc] init];
    self.checkboxButton.qmui_automaticallyAdjustTouchHighlightedInScrollView = YES;
    self.checkboxButton.qmui_outsideEdge = UIEdgeInsetsMake(-6, -6, -6, -6);
    self.checkboxButton.hidden = YES;
    [self.contentView addSubview:self.checkboxButton];
}

- (void)renderWithAsset:(QMUIAsset *)asset referenceSize:(CGSize)referenceSize {
    self.assetIdentifier = asset.identifier;
    
    // 异步请求资源对应的缩略图
    [asset requestThumbnailImageWithSize:referenceSize completion:^(UIImage *result, NSDictionary *info) {
        if ([self.assetIdentifier isEqualToString:asset.identifier]) {
            self.contentImageView.image = result;
        } else {
            self.contentImageView.image = nil;
        }
    }];
    
    if (asset.assetType == QMUIAssetTypeVideo) {
        [self initVideoDurationLabelIfNeeded];
        self.videoDurationLabel.text = [NSString qmui_timeStringWithMinsAndSecsFromSecs:asset.duration];
        self.videoDurationLabel.hidden = NO;
    } else {
        self.videoDurationLabel.hidden = YES;
    }
    
    self.favoriteImageView.hidden = !asset.phAsset.favorite;
    
    self.bottomShadowLayer.hidden = !((self.videoDurationLabel && !self.videoDurationLabel.hidden) || !self.favoriteImageView.hidden);
    
    [self setNeedsLayout];
}

- (void)layoutSubviews {
    [super layoutSubviews];
    self.contentImageView.frame = self.contentView.bounds;
    if (_selectable) {
        self.checkboxButton.frame = CGRectSetXY(self.checkboxButton.frame, CGRectGetWidth(self.contentView.bounds) - self.checkboxButtonMargins.right - CGRectGetWidth(self.checkboxButton.bounds), self.checkboxButtonMargins.top);
    }
    
    CGFloat bottomShadowLayerHeight = 0;
    
    if (!self.favoriteImageView.hidden) {
        self.favoriteImageView.frame = CGRectSetXY(self.favoriteImageView.frame, self.favoriteImageMargins.left, CGRectGetHeight(self.contentView.bounds) - self.favoriteImageMargins.bottom - CGRectGetHeight(self.favoriteImageView.frame));
        bottomShadowLayerHeight = CGRectGetHeight(self.favoriteImageView.frame) + UIEdgeInsetsGetVerticalValue(self.favoriteImageMargins);
    }
    
    if (self.videoDurationLabel && !self.videoDurationLabel.hidden) {
        [self.videoDurationLabel sizeToFit];
        self.videoDurationLabel.frame = CGRectSetXY(self.videoDurationLabel.frame, CGRectGetWidth(self.contentView.bounds) - self.videoDurationLabelMargins.right - CGRectGetWidth(self.videoDurationLabel.frame), CGRectGetHeight(self.contentView.bounds) - self.videoDurationLabelMargins.bottom - CGRectGetHeight(self.videoDurationLabel.frame));
        bottomShadowLayerHeight = MAX(bottomShadowLayerHeight, CGRectGetHeight(self.videoDurationLabel.frame) + UIEdgeInsetsGetVerticalValue(self.videoDurationLabelMargins));
    }
    
    if (!self.bottomShadowLayer.hidden) {
        self.bottomShadowLayer.frame = CGRectMake(0, CGRectGetHeight(self.contentView.bounds) - bottomShadowLayerHeight, CGRectGetWidth(self.contentView.bounds), bottomShadowLayerHeight);
    }
}

- (void)setFavoriteImage:(UIImage *)favoriteImage {
    if (![self.favoriteImage isEqual:favoriteImage]) {
        self.favoriteImageView.image = favoriteImage;
        [self.favoriteImageView sizeToFit];
        [self setNeedsLayout];
    }
    _favoriteImage = favoriteImage;
}

- (void)setCheckboxImage:(UIImage *)checkboxImage {
    if (![self.checkboxImage isEqual:checkboxImage]) {
        [self.checkboxButton setImage:checkboxImage forState:UIControlStateNormal];
        [self.checkboxButton sizeToFit];
        [self setNeedsLayout];
    }
    _checkboxImage = checkboxImage;
}

- (void)setCheckboxCheckedImage:(UIImage *)checkboxCheckedImage {
    if (![self.checkboxCheckedImage isEqual:checkboxCheckedImage]) {
        [self.checkboxButton setImage:checkboxCheckedImage forState:UIControlStateSelected];
        [self.checkboxButton setImage:checkboxCheckedImage forState:UIControlStateSelected|UIControlStateHighlighted];
        [self.checkboxButton sizeToFit];
        [self setNeedsLayout];
    }
    _checkboxCheckedImage = checkboxCheckedImage;
}

- (void)setVideoDurationLabelFont:(UIFont *)videoDurationLabelFont {
    if (![self.videoDurationLabelFont isEqual:videoDurationLabelFont]) {
        _videoDurationLabel.font = videoDurationLabelFont;
        [_videoDurationLabel qmui_calculateHeightAfterSetAppearance];
        [self setNeedsLayout];
    }
    _videoDurationLabelFont = videoDurationLabelFont;
}

- (void)setVideoDurationLabelTextColor:(UIColor *)videoDurationLabelTextColor {
    if (![self.videoDurationLabelTextColor isEqual:videoDurationLabelTextColor]) {
        _videoDurationLabel.textColor = videoDurationLabelTextColor;
    }
    _videoDurationLabelTextColor = videoDurationLabelTextColor;
}

- (void)setChecked:(BOOL)checked {
    _checked = checked;
    if (_selectable) {
        self.checkboxButton.selected = checked;
        [QMUIImagePickerHelper removeSpringAnimationOfImageCheckedWithCheckboxButton:self.checkboxButton];
        if (checked) {
            [QMUIImagePickerHelper springAnimationOfImageCheckedWithCheckboxButton:self.checkboxButton];
        }
    }
}

- (void)setSelectable:(BOOL)editing {
    _selectable = editing;
    if (self.downloadStatus == QMUIAssetDownloadStatusSucceed) {
        self.checkboxButton.hidden = !_selectable;
    }
}

- (void)setDownloadStatus:(QMUIAssetDownloadStatus)downloadStatus {
    _downloadStatus = downloadStatus;
    if (_selectable) {
        self.checkboxButton.hidden = !_selectable;
    }
}

- (void)initVideoDurationLabelIfNeeded {
    if (!self.videoDurationLabel) {
        _videoDurationLabel = [[UILabel alloc] qmui_initWithFont:self.videoDurationLabelFont textColor:self.videoDurationLabelTextColor];
        [self.contentView addSubview:_videoDurationLabel];
        [self setNeedsLayout];
    }
}

@end


================================================
FILE: QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerHelper.h
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

//
//  QMUIImagePickerHelper.h
//  qmui
//
//  Created by QMUI Team on 15/5/9.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "QMUIAsset.h"
#import "QMUIAssetsGroup.h"

/**
 *  配合 QMUIImagePickerViewController 使用的工具类
 */
@interface QMUIImagePickerHelper : NSObject

/**
 *  选中图片数量改变时,展示图片数量的 Label 的动画,动画过程如下:
 *  Label 背景色改为透明,同时产生一个与背景颜色和形状、大小都相同的图形置于 Label 底下,做先缩小再放大的 spring 动画
 *  动画结束后移除该图形,并恢复 Label 的背景色
 *
 *  @warning iOS6 下降级处理不调用动画效果
 *
 *  @param label 需要做动画的 UILabel
 */
+ (void)springAnimationOfImageSelectedCountChangeWithCountLabel:(UILabel *)label;

/**
 *  图片 checkBox 被选中时的动画
 *  @warning iOS6 下降级处理不调用动画效果
 *
 *  @param button 需要做动画的 checkbox 按钮
 */
+ (void)springAnimationOfImageCheckedWithCheckboxButton:(UIButton *)button;

/**
 * 搭配<i>springAnimationOfImageCheckedWithCheckboxButton:</i>一起使用,添加animation之前建议先remove
 */
+ (void)removeSpringAnimationOfImageCheckedWithCheckboxButton:(UIButton *)button;


/**
 *  获取最近一次调用 updateLastAlbumWithAssetsGroup 方法调用时储存的 QMUIAssetsGroup 对象
 *
 *  @param userIdentify 用户标识,由于每个用户可能需要分开储存一个最近调用过的 QMUIAssetsGroup,因此增加一个标识区分用户。
 *  一个常见的应用场景是选择图片时保存图片所在相册的对应的 QMUIAssetsGroup,并使用用户的 user id 作为区分不同用户的标识,
 *  当用户再次选择图片时可以根据已经保存的 QMUIAssetsGroup 直接进入上次使用过的相册。
 */
+ (QMUIAssetsGroup *)assetsGroupOfLastPickerAlbumWithUserIdentify:(NSString *)userIdentify;

/**
 *  储存一个 QMUIAssetsGroup,从而储存一个对应的相册,与 assetsGroupOfLatestPickerAlbumWithUserIdentify 方法对应使用
 *
 *  @param assetsGroup   要被储存的 QMUIAssetsGroup
 *  @param albumContentType 相册的内容类型
 *  @param userIdentify 用户标识,由于每个用户可能需要分开储存一个最近调用过的 QMUIAssetsGroup,因此增加一个标识区分用户
 */
+ (void)updateLastestAlbumWithAssetsGroup:(QMUIAssetsGroup *)assetsGroup ablumContentType:(QMUIAlbumContentType)albumContentType userIdentify:(NSString *)userIdentify;

/**
 * 检测一组资源是否全部下载成功,如果有资源仍未从 iCloud 中下载成功,则返回 NO
 *
 * 可以用于选择图片后,业务需要自行处理 iCloud 下载的场景。
 */
+ (BOOL)imageAssetsDownloaded:(NSMutableArray<QMUIAsset *> *)imagesAssetArray;

/**
 * 检测资源是否已经在本地,如果资源仍未从 iCloud 中成功下载,则会发出请求从 iCloud 加载资源,并通过多次调用 block 返回请求结果
 *
 * 可以用于选择图片后,业务需要自行处理 iCloud 下载的场景。
 */
+ (void)requestImageAssetIfNeeded:(QMUIAsset *)asset completion: (void (^)(QMUIAssetDownloadStatus downloadStatus, NSError *error))completion;

@end


================================================
FILE: QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerHelper.m
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

//
//  QMUIImagePickerHelper.m
//  qmui
//
//  Created by QMUI Team on 15/5/9.
//

#import "QMUIImagePickerHelper.h"
#import "QMUICore.h"
#import "QMUIAssetsManager.h"
#import "QMUIAsset.h"
#import <Photos/PHCollection.h>
#import <Photos/PHFetchResult.h>
#import "UIImage+QMUI.h"
#import "QMUILog.h"

static NSString * const kLastAlbumKeyPrefix = @"QMUILastestAlbumKeyWith";
static NSString * const kContentTypeOfLastAlbumKeyPrefix = @"QMUIContentTypeOfLastestAlbumKeyWith";

@implementation QMUIImagePickerHelper

+ (void)springAnimationOfImageSelectedCountChangeWithCountLabel:(UILabel *)label {
    [self actionSpringAnimationForView:label];
}

+ (void)springAnimationOfImageCheckedWithCheckboxButton:(UIButton *)button {
    [self actionSpringAnimationForView:button];
}

+ (void)actionSpringAnimationForView:(UIView *)view {
    NSTimeInterval duration = 0.6;
    CAKeyframeAnimation *springAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
    springAnimation.values = @[@.85, @1.15, @.9, @1.0,];
    springAnimation.keyTimes = @[@(0.0 / duration), @(0.15 / duration) , @(0.3 / duration), @(0.45 / duration),];
    springAnimation.duration = duration;
    [view.layer addAnimation:springAnimation forKey:@"imagePickerActionSpring"];
}

+ (void)removeSpringAnimationOfImageCheckedWithCheckboxButton:(UIButton *)button {
    [button.layer removeAnimationForKey:@"imagePickerActionSpring"];
}

+ (QMUIAssetsGroup *)assetsGroupOfLastPickerAlbumWithUserIdentify:(NSString *)userIdentify {
    // 获取 NSUserDefaults,里面储存了所有 updateLastestAlbumWithAssetsGroup 的结果
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    // 使用特定的前缀和可以标记不同用户的字符串拼接成 key,用于获取当前用户最近调用 updateLastestAlbumWithAssetsGroup 储存的相册以及对于的 QMUIAlbumContentType 值
    NSString *lastAlbumKey = [NSString stringWithFormat:@"%@%@", kLastAlbumKeyPrefix, userIdentify];
    NSString *contentTypeOflastAlbumKey = [NSString stringWithFormat:@"%@%@", kContentTypeOfLastAlbumKeyPrefix, userIdentify];
    
    __block QMUIAssetsGroup *assetsGroup;
    
    QMUIAlbumContentType albumContentType = (QMUIAlbumContentType)[userDefaults integerForKey:contentTypeOflastAlbumKey];
    
    NSString *groupIdentifier = [userDefaults valueForKey:lastAlbumKey];
    /**
     *  如果获取到的 PHAssetCollection localIdentifier 不为空,则获取该 URL 对应的相册。
     *  在 QMUI 2.0.0 及较早的版本中,QMUI 兼容 AssetsLibrary 的使用,
     *  因此原来储存的 groupIdentifier 实际上可能会是一个 NSURL 而不是我们需要的 NSString,
     *  所以这里还需要判断一下实际拿到的数据的类型是否为 NSString,如果是才继续进行。
     */
    if (groupIdentifier && [groupIdentifier isKindOfClass:[NSString class]]) {
        PHFetchResult *phFetchResult = [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers:@[groupIdentifier] options:nil];
        if (phFetchResult.count > 0) {
            // 创建一个 PHFetchOptions,用于对内容类型进行控制
            PHFetchOptions *phFetchOptions;
            // 旧版本中没有存储 albumContentType,因此为了防止 crash,这里做一下判断
            if (albumContentType) {
                phFetchOptions = [PHPhotoLibrary createFetchOptionsWithAlbumContentType:albumContentType];
            }
            PHAssetCollection *phAssetCollection = [phFetchResult firstObject];
            assetsGroup = [[QMUIAssetsGroup alloc] initWithPHCollection:phAssetCollection fetchAssetsOptions:phFetchOptions];
        }
    } else {
        QMUILog(@"QMUIImagePickerLibrary", @"Group For localIdentifier is not found! groupIdentifier is %@", groupIdentifier);
    }
    return assetsGroup;
}

+ (void)updateLastestAlbumWithAssetsGroup:(QMUIAssetsGroup *)assetsGroup ablumContentType:(QMUIAlbumContentType)albumContentType userIdentify:(NSString *)userIdentify {
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    // 使用特定的前缀和可以标记不同用户的字符串拼接成 key,用于为当前用户储存相册对应的 QMUIAssetsGroup 与 QMUIAlbumContentType
    NSString *lastAlbumKey = [NSString stringWithFormat:@"%@%@", kLastAlbumKeyPrefix, userIdentify];
    NSString *contentTypeOflastAlbumKey = [NSString stringWithFormat:@"%@%@", kContentTypeOfLastAlbumKeyPrefix, userIdentify];
    [userDefaults setValue:assetsGroup.phAssetCollection.localIdentifier forKey:lastAlbumKey];
    [userDefaults setInteger:albumContentType forKey:contentTypeOflastAlbumKey];
    [userDefaults synchronize];
}

+ (BOOL)imageAssetsDownloaded:(NSMutableArray<QMUIAsset *> *)imagesAssetArray {
    for (QMUIAsset *asset in imagesAssetArray) {
        if (asset.downloadStatus != QMUIAssetDownloadStatusSucceed) {
            return NO;
        }
    }
    return YES;
}

+ (void)requestImageAssetIfNeeded:(QMUIAsset *)asset completion: (void (^)(QMUIAssetDownloadStatus downloadStatus, NSError *error))completion {
    if (asset.downloadStatus != QMUIAssetDownloadStatusSucceed) {
        
        // 资源加载中
        if (completion) {
            completion(QMUIAssetDownloadStatusDownloading, nil);
        }

        [asset requestOriginImageWithCompletion:^(UIImage *result, NSDictionary<NSString *,id> *info) {
            BOOL downloadSucceed = (result && !info) || (![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey] && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue]);
            
            if (downloadSucceed) {
                // 资源资源已经在本地或下载成功
                [asset updateDownloadStatusWithDownloadResult:YES];
                
                if (completion) {
                    completion(QMUIAssetDownloadStatusSucceed, nil);
                }
                
            } else if ([info objectForKey:PHImageErrorKey]) {
                // 下载错误
                [asset updateDownloadStatusWithDownloadResult:NO];
                
                if (completion) {
                    completion(QMUIAssetDownloadStatusFailed, [info objectForKey:PHImageErrorKey]);
                }
            }
        } withProgressHandler:^(double progress, NSError * _Nullable error, BOOL * _Nonnull stop, NSDictionary * _Nullable info) {
            QMUILog(@"QMUIImagePickerLibrary", @"current progress is %f", progress);
            asset.downloadProgress = progress;
        }];
    } else {
        // 资源资源已经在本地或下载成功
        if (completion) {
            completion(QMUIAssetDownloadStatusSucceed, nil);
        }
    }
}

@end




================================================
FILE: QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerPreviewViewController.h
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

//
//  QMUIImagePickerPreviewViewController.h
//  qmui
//
//  Created by QMUI Team on 15/5/3.
//

#import <UIKit/UIKit.h>
#import "QMUIImagePreviewViewController.h"
#import "QMUIAsset.h"

NS_ASSUME_NONNULL_BEGIN

@class QMUIButton, QMUINavigationButton;
@class QMUIImagePickerViewController;
@class QMUIImagePickerPreviewViewController;

@protocol QMUIImagePickerPreviewViewControllerDelegate <NSObject>

@optional

/// 取消选择图片后被调用
- (void)imagePickerPreviewViewControllerDidCancel:(QMUIImagePickerPreviewViewController *)imagePickerPreviewViewController;
/// 即将选中图片
- (void)imagePickerPreviewViewController:(QMUIImagePickerPreviewViewController *)imagePickerPreviewViewController willCheckImageAtIndex:(NSInteger)index;
/// 已经选中图片
- (void)imagePickerPreviewViewController:(QMUIImagePickerPreviewViewController *)imagePickerPreviewViewController didCheckImageAtIndex:(NSInteger)index;
/// 即将取消选中图片
- (void)imagePickerPreviewViewController:(QMUIImagePickerPreviewViewController *)imagePickerPreviewViewController willUncheckImageAtIndex:(NSInteger)index;
/// 已经取消选中图片
- (void)imagePickerPreviewViewController:(QMUIImagePickerPreviewViewController *)imagePickerPreviewViewController didUncheckImageAtIndex:(NSInteger)index;

@end


@interface QMUIImagePickerPreviewViewController : QMUIImagePreviewViewController <QMUIImagePreviewViewDelegate>

@property(nullable, nonatomic, weak) id<QMUIImagePickerPreviewViewControllerDelegate> delegate;

@property(nullable, nonatomic, strong) UIColor *toolBarBackgroundColor UI_APPEARANCE_SELECTOR;
@property(nullable, nonatomic, strong) UIColor *toolBarTintColor UI_APPEARANCE_SELECTOR;

@property(nullable, nonatomic, strong, readonly) UIView *topToolBarView;
@property(nullable, nonatomic, strong, readonly) QMUINavigationButton *backButton;
@property(nullable, nonatomic, strong, readonly) QMUIButton *checkboxButton;

/// 由于组件需要通过本地图片的 QMUIAsset 对象读取图片的详细信息,因此这里的需要传入的是包含一个或多个 QMUIAsset 对象的数组
@property(nullable, nonatomic, strong) NSMutableArray<QMUIAsset *> *imagesAssetArray;
@property(nullable, nonatomic, strong) NSMutableArray<QMUIAsset *> *selectedImageAssetArray;

@property(nonatomic, assign) QMUIAssetDownloadStatus downloadStatus;

/// 最多可以选择的图片数,默认为无穷大
@property(nonatomic, assign) NSUInteger maximumSelectImageCount;
/// 最少需要选择的图片数,默认为 0
@property(nonatomic, assign) NSUInteger minimumSelectImageCount;
/// 选择图片超出最大图片限制时 alertView 的标题
@property(nullable, nonatomic, copy) NSString *alertTitleWhenExceedMaxSelectImageCount;
/// 选择图片超出最大图片限制时 alertView 的标题
@property(nullable, nonatomic, copy) NSString *alertButtonTitleWhenExceedMaxSelectImageCount;

/**
 *  更新数据并刷新 UI,手工调用
 *
 *  @param imageAssetArray         包含所有需要展示的图片的数组
 *  @param selectedImageAssetArray 包含所有需要展示的图片中已经被选中的图片的数组
 *  @param currentImageIndex       当前展示的图片在 imageAssetArray 的索引
 *  @param singleCheckMode         是否为单选模式,如果是单选模式,则不显示 checkbox
 */
- (void)updateImagePickerPreviewViewWithImagesAssetArray:(NSMutableArray<QMUIAsset *> * _Nullable)imageAssetArray
                                 selectedImageAssetArray:(NSMutableArray<QMUIAsset *> * _Nullable)selectedImageAssetArray
                                       currentImageIndex:(NSInteger)currentImageIndex
                                         singleCheckMode:(BOOL)singleCheckMode;

@end


@interface QMUIImagePickerPreviewViewController (UIAppearance)

+ (instancetype)appearance;

@end

NS_ASSUME_NONNULL_END


================================================
FILE: QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerPreviewViewController.m
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

//
//  QMUIImagePickerPreviewViewController.m
//  qmui
//
//  Created by QMUI Team on 15/5/3.
//

#import "QMUIImagePickerPreviewViewController.h"
#import "QMUICore.h"
#import "QMUIImagePickerViewController.h"
#import "QMUIImagePickerHelper.h"
#import "QMUIAssetsManager.h"
#import "QMUIZoomImageView.h"
#import "QMUIAsset.h"
#import "QMUIButton.h"
#import "QMUINavigationButton.h"
#import "QMUIImagePickerHelper.h"
#import "QMUIPieProgressView.h"
#import "QMUIAlertController.h"
#import "UIImage+QMUI.h"
#import "UIView+QMUI.h"
#import "QMUILog.h"
#import "QMUIAppearance.h"

#pragma mark - QMUIImagePickerPreviewViewController (UIAppearance)

@implementation QMUIImagePickerPreviewViewController (UIAppearance)

+ (instancetype)appearance {
    return [QMUIAppearance appearanceForClass:self];
}

+ (void)initialize {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self initAppearance];
    });
}

+ (void)initAppearance {
    QMUIImagePickerPreviewViewController.appearance.toolBarBackgroundColor = UIColorMakeWithRGBA(27, 27, 27, .9f);
    QMUIImagePickerPreviewViewController.appearance.toolBarTintColor = UIColorWhite;
}

@end

@implementation QMUIImagePickerPreviewViewController {
    BOOL _singleCheckMode;
}

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
        self.maximumSelectImageCount = INT_MAX;
        self.minimumSelectImageCount = 0;
        
        [self qmui_applyAppearance];
    }
    return self;
}

- (void)initSubviews {
    [super initSubviews];
    
    self.imagePreviewView.delegate = self;
    
    _topToolBarView = [[UIView alloc] init];
    self.topToolBarView.backgroundColor = self.toolBarBackgroundColor;
    self.topToolBarView.tintColor = self.toolBarTintColor;
    [self.view addSubview:self.topToolBarView];
    
    _backButton = [[QMUINavigationButton alloc] initWithType:QMUINavigationButtonTypeBack];
    [self.backButton addTarget:self action:@selector(handleCancelPreviewImage:) forControlEvents:UIControlEventTouchUpInside];
    self.backButton.qmui_outsideEdge = UIEdgeInsetsMake(-30, -20, -50, -80);
    [self.topToolBarView addSubview:self.backButton];
    
    _checkboxButton = [[QMUIButton alloc] init];
    self.checkboxButton.adjustsTitleTintColorAutomatically = YES;
    self.checkboxButton.adjustsImageTintColorAutomatically = YES;
    UIImage *checkboxImage = [QMUIHelper imageWithName:@"QMUI_previewImage_checkbox"];
    UIImage *checkedCheckboxImage = [QMUIHelper imageWithName:@"QMUI_previewImage_checkbox_checked"];
    [self.checkboxButton setImage:checkboxImage forState:UIControlStateNormal];
    [self.checkboxButton setImage:checkedCheckboxImage forState:UIControlStateSelected];
    [self.checkboxButton setImage:[self.checkboxButton imageForState:UIControlStateSelected] forState:UIControlStateSelected|UIControlStateHighlighted];
    [self.checkboxButton sizeToFit];
    [self.checkboxButton addTarget:self action:@selector(handleCheckButtonClick:) forControlEvents:UIControlEventTouchUpInside];
    self.checkboxButton.qmui_outsideEdge = UIEdgeInsetsMake(-6, -6, -6, -6);
    [self.topToolBarView addSubview:self.checkboxButton];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    if (!_singleCheckMode) {
        QMUIAsset *imageAsset = self.imagesAssetArray[self.imagePreviewView.currentImageIndex];
        self.checkboxButton.selected = [self.selectedImageAssetArray containsObject:imageAsset];
    }
    
    if ([self conformsToProtocol:@protocol(QMUICustomNavigationBarTransitionDelegate)]) {
        UIViewController<QMUICustomNavigationBarTransitionDelegate> *vc = (UIViewController<QMUICustomNavigationBarTransitionDelegate> *)self;
        if ([vc respondsToSelector:@selector(shouldCustomizeNavigationBarTransitionIfHideable)] &&
            [vc shouldCustomizeNavigationBarTransitionIfHideable]) {
        } else {
            [self.navigationController setNavigationBarHidden:YES animated:NO];
        }
    }
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    
    if ([self conformsToProtocol:@protocol(QMUICustomNavigationBarTransitionDelegate)]) {
        UIViewController<QMUICustomNavigationBarTransitionDelegate> *vc = (UIViewController<QMUICustomNavigationBarTransitionDelegate> *)self;
        if ([vc respondsToSelector:@selector(shouldCustomizeNavigationBarTransitionIfHideable)] &&
            [vc shouldCustomizeNavigationBarTransitionIfHideable]) {
        } else {
            [self.navigationController setNavigationBarHidden:NO animated:NO];
        }
    }
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    self.topToolBarView.frame = CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), NavigationContentTopConstant);
    CGFloat topToolbarPaddingTop = SafeAreaInsetsConstantForDeviceWithNotch.top;
    CGFloat topToolbarContentHeight = CGRectGetHeight(self.topToolBarView.bounds) - topToolbarPaddingTop;
    self.backButton.frame = CGRectSetXY(self.backButton.frame, 16 + self.view.safeAreaInsets.left, topToolbarPaddingTop + CGFloatGetCenter(topToolbarContentHeight, CGRectGetHeight(self.backButton.frame)));
    if (!self.checkboxButton.hidden) {
        self.checkboxButton.frame = CGRectSetXY(self.checkboxButton.frame, CGRectGetWidth(self.topToolBarView.frame) - 10 - self.view.safeAreaInsets.right - CGRectGetWidth(self.checkboxButton.frame), topToolbarPaddingTop + CGFloatGetCenter(topToolbarContentHeight, CGRectGetHeight(self.checkboxButton.frame)));
    }
}

- (BOOL)preferredNavigationBarHidden {
    return YES;
}

- (BOOL)prefersStatusBarHidden {
    return YES;
}

- (void)setToolBarBackgroundColor:(UIColor *)toolBarBackgroundColor {
    _toolBarBackgroundColor = toolBarBackgroundColor;
    self.topToolBarView.backgroundColor = self.toolBarBackgroundColor;
}

- (void)setToolBarTintColor:(UIColor *)toolBarTintColor {
    _toolBarTintColor = toolBarTintColor;
    self.topToolBarView.tintColor = toolBarTintColor;
}

- (void)setDownloadStatus:(QMUIAssetDownloadStatus)downloadStatus {
    _downloadStatus = downloadStatus;
    if (!_singleCheckMode) {
        self.checkboxButton.hidden = NO;
    }
}

- (void)updateImagePickerPreviewViewWithImagesAssetArray:(NSMutableArray<QMUIAsset *> *)imageAssetArray
                                 selectedImageAssetArray:(NSMutableArray<QMUIAsset *> *)selectedImageAssetArray
                                       currentImageIndex:(NSInteger)currentImageIndex
                                         singleCheckMode:(BOOL)singleCheckMode {
    self.imagesAssetArray = imageAssetArray;
    self.selectedImageAssetArray = selectedImageAssetArray;
    self.imagePreviewView.currentImageIndex = currentImageIndex;
    _singleCheckMode = singleCheckMode;
    if (singleCheckMode) {
        self.checkboxButton.hidden = YES;
    }
}

#pragma mark - <QMUIImagePreviewViewDelegate>

- (NSUInteger)numberOfImagesInImagePreviewView:(QMUIImagePreviewView *)imagePreviewView {
    return [self.imagesAssetArray count];
}

- (QMUIImagePreviewMediaType)imagePreviewView:(QMUIImagePreviewView *)imagePreviewView assetTypeAtIndex:(NSUInteger)index {
    QMUIAsset *imageAsset = [self.imagesAssetArray objectAtIndex:index];
    if (imageAsset.assetType == QMUIAssetTypeImage) {
        if (imageAsset.assetSubType == QMUIAssetSubTypeLivePhoto) {
            return QMUIImagePreviewMediaTypeLivePhoto;
        }
        return QMUIImagePreviewMediaTypeImage;
    } else if (imageAsset.assetType == QMUIAssetTypeVideo) {
        return QMUIImagePreviewMediaTypeVideo;
    } else {
        return QMUIImagePreviewMediaTypeOthers;
    }
}

- (void)imagePreviewView:(QMUIImagePreviewView *)imagePreviewView renderZoomImageView:(QMUIZoomImageView *)zoomImageView atIndex:(NSUInteger)index {
    [self requestImageForZoomImageView:zoomImageView withIndex:index];
}

- (void)imagePreviewView:(QMUIImagePreviewView *)imagePreviewView willScrollHalfToIndex:(NSUInteger)index {
    if (!_singleCheckMode) {
        QMUIAsset *imageAsset = self.imagesAssetArray[index];
        self.checkboxButton.selected = [self.selectedImageAssetArray containsObject:imageAsset];
    }
}

#pragma mark - <QMUIZoomImageViewDelegate>

- (void)singleTouchInZoomingImageView:(QMUIZoomImageView *)zoomImageView location:(CGPoint)location {
    self.topToolBarView.hidden = !self.topToolBarView.hidden;
}

- (void)didTouchICloudRetryButtonInZoomImageView:(QMUIZoomImageView *)imageView {
    NSInteger index = [self.imagePreviewView indexForZoomImageView:imageView];
    [self.imagePreviewView.collectionView reloadItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:index inSection:0]]];
}

- (void)zoomImageView:(QMUIZoomImageView *)imageView didHideVideoToolbar:(BOOL)didHide {
    self.topToolBarView.hidden = didHide;
}

#pragma mark - 按钮点击回调

- (void)handleCancelPreviewImage:(QMUIButton *)button {
    if (self.navigationController) {
        [self.navigationController popViewControllerAnimated:YES];
    } else {
//        [self exitPreviewAutomatically];
    }
    if (self.delegate && [self.delegate respondsToSelector:@selector(imagePickerPreviewViewControllerDidCancel:)]) {
        [self.delegate imagePickerPreviewViewControllerDidCancel:self];
    }
}

- (void)handleCheckButtonClick:(QMUIButton *)button {
    [QMUIImagePickerHelper removeSpringAnimationOfImageCheckedWithCheckboxButton:button];
    
    if (button.selected) {
        if ([self.delegate respondsToSelector:@selector(imagePickerPreviewViewController:willUncheckImageAtIndex:)]) {
            [self.delegate imagePickerPreviewViewController:self willUncheckImageAtIndex:self.imagePreviewView.currentImageIndex];
        }
        
        button.selected = NO;
        QMUIAsset *imageAsset = self.imagesAssetArray[self.imagePreviewView.currentImageIndex];
        [self.selectedImageAssetArray removeObject:imageAsset];
        
        if ([self.delegate respondsToSelector:@selector(imagePickerPreviewViewController:didUncheckImageAtIndex:)]) {
            [self.delegate imagePickerPreviewViewController:self didUncheckImageAtIndex:self.imagePreviewView.currentImageIndex];
        }
    } else {
        if ([self.selectedImageAssetArray count] >= self.maximumSelectImageCount) {
            if (!self.alertTitleWhenExceedMaxSelectImageCount) {
                self.alertTitleWhenExceedMaxSelectImageCount = [NSString stringWithFormat:@"你最多只能选择%@张图片", @(self.maximumSelectImageCount)];
            }
            if (!self.alertButtonTitleWhenExceedMaxSelectImageCount) {
                self.alertButtonTitleWhenExceedMaxSelectImageCount = [NSString stringWithFormat:@"我知道了"];
            }
            
            QMUIAlertController *alertController = [QMUIAlertController alertControllerWithTitle:self.alertTitleWhenExceedMaxSelectImageCount message:nil preferredStyle:QMUIAlertControllerStyleAlert];
            [alertController addAction:[QMUIAlertAction actionWithTitle:self.alertButtonTitleWhenExceedMaxSelectImageCount style:QMUIAlertActionStyleCancel handler:nil]];
            [alertController showWithAnimated:YES];
            return;
        }
        
        if (self.delegate && [self.delegate respondsToSelector:@selector(imagePickerPreviewViewController:willCheckImageAtIndex:)]) {
            [self.delegate imagePickerPreviewViewController:self willCheckImageAtIndex:self.imagePreviewView.currentImageIndex];
        }
        
        button.selected = YES;
        [QMUIImagePickerHelper springAnimationOfImageCheckedWithCheckboxButton:button];
        QMUIAsset *imageAsset = [self.imagesAssetArray objectAtIndex:self.imagePreviewView.currentImageIndex];
        [self.selectedImageAssetArray addObject:imageAsset];
        
        if (self.delegate && [self.delegate respondsToSelector:@selector(imagePickerPreviewViewController:didCheckImageAtIndex:)]) {
            [self.delegate imagePickerPreviewViewController:self didCheckImageAtIndex:self.imagePreviewView.currentImageIndex];
        }
    }
}

#pragma mark - Request Image

- (void)requestImageForZoomImageView:(QMUIZoomImageView *)zoomImageView withIndex:(NSInteger)index {
    QMUIZoomImageView *imageView = zoomImageView ? : [self.imagePreviewView zoomImageViewAtIndex:index];
    // 如果是走 PhotoKit 的逻辑,那么这个 block 会被多次调用,并且第一次调用时返回的图片是一张小图,
    // 拉取图片的过程中可能会多次返回结果,且图片尺寸越来越大,因此这里调整 contentMode 以防止图片大小跳动
    imageView.contentMode = UIViewContentModeScaleAspectFit;
    QMUIAsset *imageAsset = [self.imagesAssetArray objectAtIndex:index];
    // 获取资源图片的预览图,这是一张适合当前设备屏幕大小的图片,最终展示时把图片交给组件控制最终展示出来的大小。
    // 系统相册本质上也是这么处理的,因此无论是系统相册,还是这个系列组件,由始至终都没有显示照片原图,
    // 这也是系统相册能加载这么快的原因。
    // 另外这里采用异步请求获取图片,避免获取图片时 UI 卡顿
    PHAssetImageProgressHandler phProgressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
        imageAsset.downloadProgress = progress;
        dispatch_async(dispatch_get_main_queue(), ^{
            QMUILogInfo(@"QMUIImagePickerLibrary", @"Download iCloud image in preview, current progress is: %f", progress);
            
            if (self.downloadStatus != QMUIAssetDownloadStatusDownloading) {
                self.downloadStatus = QMUIAssetDownloadStatusDownloading;
                imageView.cloudDownloadStatus = QMUIAssetDownloadStatusDownloading;

                // 重置 progressView 的显示的进度为 0
                [imageView.cloudProgressView setProgress:0 animated:NO];
            }
            // 拉取资源的初期,会有一段时间没有进度,猜测是发出网络请求以及与 iCloud 建立连接的耗时,这时预先给个 0.02 的进度值,看上去好看些
            float targetProgress = fmax(0.02, progress);
            if (targetProgress < imageView.cloudProgressView.progress) {
                [imageView.cloudProgressView setProgress:targetProgress animated:NO];
            } else {
                imageView.cloudProgressView.progress = fmax(0.02, progress);
            }
            if (error) {
                QMUILog(@"QMUIImagePickerLibrary", @"Download iCloud image Failed, current progress is: %f", progress);
                self.downloadStatus = QMUIAssetDownloadStatusFailed;
                imageView.cloudDownloadStatus = QMUIAssetDownloadStatusFailed;
            }
        });
    };
    if (imageAsset.assetType == QMUIAssetTypeVideo) {
        imageView.tag = -1;
        imageAsset.requestID = [imageAsset requestPlayerItemWithCompletion:^(AVPlayerItem *playerItem, NSDictionary *info) {
            // 这里可能因为 imageView 复用,导致前面的请求得到的结果显示到别的 imageView 上,
            // 因此判断如果是新请求(无复用问题)或者是当前的请求才把获得的图片结果展示出来
            dispatch_async(dispatch_get_main_queue(), ^{
                BOOL isNewRequest = (imageView.tag == -1 && imageAsset.requestID == 0);
                BOOL isCurrentRequest = imageView.tag == imageAsset.requestID;
                BOOL loadICloudImageFault = !playerItem || info[PHImageErrorKey];
                if (!loadICloudImageFault && (isNewRequest || isCurrentRequest)) {
                    imageView.videoPlayerItem = playerItem;
                }
            });
        } withProgressHandler:phProgressHandler];
        imageView.tag = imageAsset.requestID;
    } else {
        if (imageAsset.assetType != QMUIAssetTypeImage) {
            return;
        }
        
        // 这么写是为了消除 Xcode 的 API available warning
        BOOL isLivePhoto = NO;
        if (imageAsset.assetSubType == QMUIAssetSubTypeLivePhoto) {
            isLivePhoto = YES;
            imageView.tag = -1;
            imageAsset.requestID = [imageAsset requestLivePhotoWithCompletion:^void(PHLivePhoto *livePhoto, NSDictionary *info) {
                // 这里可能因为 imageView 复用,导致前面的请求得到的结果显示到别的 imageView 上,
                // 因此判断如果是新请求(无复用问题)或者是当前的请求才把获得的图片结果展示出来
                dispatch_async(dispatch_get_main_queue(), ^{
                    BOOL isNewRequest = (imageView.tag == -1 && imageAsset.requestID == 0);
                    BOOL isCurrentRequest = imageView.tag == imageAsset.requestID;
                    BOOL loadICloudImageFault = !livePhoto || info[PHImageErrorKey];
                    if (!loadICloudImageFault && (isNewRequest || isCurrentRequest)) {
                        // 如果是走 PhotoKit 的逻辑,那么这个 block 会被多次调用,并且第一次调用时返回的图片是一张小图,
                        // 这时需要把图片放大到跟屏幕一样大,避免后面加载大图后图片的显示会有跳动
                        imageView.livePhoto = livePhoto;
                    }
                    BOOL downloadSucceed = (livePhoto && !info) || (![[info objectForKey:PHLivePhotoInfoCancelledKey] boolValue] && ![info objectForKey:PHLivePhotoInfoErrorKey] && ![[info objectForKey:PHLivePhotoInfoIsDegradedKey] boolValue]);
                    if (downloadSucceed) {
                        // 资源资源已经在本地或下载成功
                        [imageAsset updateDownloadStatusWithDownloadResult:YES];
                        self.downloadStatus = QMUIAssetDownloadStatusSucceed;
                        imageView.cloudDownloadStatus = QMUIAssetDownloadStatusSucceed;
                    } else if ([info objectForKey:PHLivePhotoInfoErrorKey] ) {
                        // 下载错误
                        [imageAsset updateDownloadStatusWithDownloadResult:NO];
                        self.downloadStatus = QMUIAssetDownloadStatusFailed;
                        imageView.cloudDownloadStatus = QMUIAssetDownloadStatusFailed;
                    }
                });
            } withProgressHandler:phProgressHandler];
            imageView.tag = imageAsset.requestID;
        }
        
        if (isLivePhoto) {
        } else if (imageAsset.assetSubType == QMUIAssetSubTypeGIF) {
            [imageAsset requestImageData:^(NSData *imageData, NSDictionary<NSString *,id> *info, BOOL isGIF, BOOL isHEIC) {
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                    UIImage *resultImage = [UIImage qmui_animatedImageWithData:imageData];
                    dispatch_async(dispatch_get_main_queue(), ^{
                        imageView.image = resultImage;
                    });
                });
            }];
        } else {
            imageView.tag = -1;
            imageView.image = [imageAsset thumbnailWithSize:CGSizeMake([QMUIImagePickerViewController appearance].minimumImageWidth, [QMUIImagePickerViewController appearance].minimumImageWidth)];
            imageAsset.requestID = [imageAsset requestOriginImageWithCompletion:^void(UIImage *result, NSDictionary *info) {
                // 这里可能因为 imageView 复用,导致前面的请求得到的结果显示到别的 imageView 上,
                // 因此判断如果是新请求(无复用问题)或者是当前的请求才把获得的图片结果展示出来
                dispatch_async(dispatch_get_main_queue(), ^{
                    BOOL isNewRequest = (imageView.tag == -1 && imageAsset.requestID == 0);
                    BOOL isCurrentRequest = imageView.tag == imageAsset.requestID;
                    BOOL loadICloudImageFault = !result || info[PHImageErrorKey];
                    if (!loadICloudImageFault && (isNewRequest || isCurrentRequest)) {
                        imageView.image = result;
                    }
                    BOOL downloadSucceed = (result && !info) || (![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey] && ![[info objectForKey:PHImageResultIsDegradedKey] boolValue]);
                    if (downloadSucceed) {
                        // 资源资源已经在本地或下载成功
                        [imageAsset updateDownloadStatusWithDownloadResult:YES];
                        self.downloadStatus = QMUIAssetDownloadStatusSucceed;
                        imageView.cloudDownloadStatus = QMUIAssetDownloadStatusSucceed;
                    } else if ([info objectForKey:PHImageErrorKey] ) {
                        // 下载错误
                        [imageAsset updateDownloadStatusWithDownloadResult:NO];
                        self.downloadStatus = QMUIAssetDownloadStatusFailed;
                        imageView.cloudDownloadStatus = QMUIAssetDownloadStatusFailed;
                    }
                });
            } withProgressHandler:phProgressHandler];
            imageView.tag = imageAsset.requestID;
        }
    }
}

@end


================================================
FILE: QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerViewController.h
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

//
//  QMUIImagePickerViewController.h
//  qmui
//
//  Created by QMUI Team on 15/5/2.
//

#import <UIKit/UIKit.h>
#import "QMUICommonViewController.h"
#import "QMUIImagePickerPreviewViewController.h"
#import "QMUIAsset.h"
#import "QMUIAssetsGroup.h"

NS_ASSUME_NONNULL_BEGIN

@class QMUIImagePickerViewController;
@class QMUIButton;

@protocol QMUIImagePickerViewControllerDelegate <NSObject>

@optional

/**
 *  创建一个 ImagePickerPreviewViewController 用于预览图片
 */
- (QMUIImagePickerPreviewViewController *)imagePickerPreviewViewControllerForImagePickerViewController:(QMUIImagePickerViewController *)imagePickerViewController;

/**
 *  控制照片的排序,若不实现,默认为 QMUIAlbumSortTypePositive
 *  @note 注意返回值会决定第一次进来相片列表时列表默认的滚动位置,如果为 QMUIAlbumSortTypePositive,则列表默认滚动到底部,如果为 QMUIAlbumSortTypeReverse,则列表默认滚动到顶部。
 */
- (QMUIAlbumSortType)albumSortTypeForImagePickerViewController:(QMUIImagePickerViewController *)imagePickerViewController;

/**
 *  多选模式下选择图片完毕后被调用(点击 sendButton 后被调用),单选模式下没有底部发送按钮,所以也不会走到这个delegate
 *
 *  @param imagePickerViewController 对应的 QMUIImagePickerViewController
 *  @param imagesAssetArray          包含被选择的图片的 QMUIAsset 对象的数组。
 */
- (void)imagePickerViewController:(QMUIImagePickerViewController *)imagePickerViewController didFinishPickingImageWithImagesAssetArray:(NSMutableArray<QMUIAsset *> *)imagesAssetArray;

/**
 *  cell 被点击时调用(先调用这个接口,然后才去走预览大图的逻辑),注意这并非指选中 checkbox 事件
 *
 *  @param imagePickerViewController        对应的 QMUIImagePickerViewController
 *  @param imageAsset                       被选中的图片的 QMUIAsset 对象
 *  @param imagePickerPreviewViewController 选中图片后进行图片预览的 viewController
 */
- (void)imagePickerViewController:(QMUIImagePickerViewController *)imagePickerViewController didSelectImageWithImagesAsset:(QMUIAsset *)imageAsset afterImagePickerPreviewViewControllerUpdate:(QMUIImagePickerPreviewViewController *)imagePickerPreviewViewController;

/// 是否能够选中 checkbox
- (BOOL)imagePickerViewController:(QMUIImagePickerViewController *)imagePickerViewController shouldCheckImageAtIndex:(NSInteger)index;

/// 即将选中 checkbox 时调用
- (void)imagePickerViewController:(QMUIImagePickerViewController *)imagePickerViewController willCheckImageAtIndex:(NSInteger)index;

/// 选中了 checkbox 之后调用
- (void)imagePickerViewController:(QMUIImagePickerViewController *)imagePickerViewController didCheckImageAtIndex:(NSInteger)index;

/// 即将取消选中 checkbox 时调用
- (void)imagePickerViewController:(QMUIImagePickerViewController *)imagePickerViewController willUncheckImageAtIndex:(NSInteger)index;

/// 取消了 checkbox 选中之后调用
- (void)imagePickerViewController:(QMUIImagePickerViewController *)imagePickerViewController didUncheckImageAtIndex:(NSInteger)index;

/**
 *  取消选择图片后被调用
 */
- (void)imagePickerViewControllerDidCancel:(QMUIImagePickerViewController *)imagePickerViewController;

/**
 *  即将需要显示 Loading 时调用
 *
 *  @see shouldShowDefaultLoadingView
 */
- (void)imagePickerViewControllerWillStartLoading:(QMUIImagePickerViewController *)imagePickerViewController;

/**
 *  即将需要隐藏 Loading 时调用
 *
 *  @see shouldShowDefaultLoadingView
 */
- (void)imagePickerViewControllerDidFinishLoading:(QMUIImagePickerViewController *)imagePickerViewController;

@end


@interface QMUIImagePickerViewController : QMUICommonViewController <UICollectionViewDataSource, UICollectionViewDelegate, QMUIImagePickerPreviewViewControllerDelegate>

@property(nullable, nonatomic, weak) id<QMUIImagePickerViewControllerDelegate> imagePickerViewControllerDelegate;

/*
 * 图片的最小尺寸,布局时如果有剩余空间,会将空间分配给图片大小,所以最终显示出来的大小不一定等于minimumImageWidth。默认是75。
 * @warning collectionViewLayout 和 collectionView 可能有设置 sectionInsets 和 contentInsets,所以设置几行不可以简单的通过 screenWdith / columnCount 来获得
 */
@property(nonatomic, assign) CGFloat minimumImageWidth UI_APPEARANCE_SELECTOR;

@property(nullable, nonatomic, strong, readonly) UICollectionViewFlowLayout *collectionViewLayout;
@property(nullable, nonatomic, strong, readonly) UICollectionView *collectionView;

@property(nullable, nonatomic, strong, readonly) UIView *operationToolBarView;
@property(nullable, nonatomic, strong, readonly) QMUIButton *previewButton;
@property(nullable, nonatomic, strong, readonly) QMUIButton *sendButton;
@property(nullable, nonatomic, strong, readonly) UILabel *imageCountLabel;

/// 也可以直接传入 QMUIAssetsGroup,然后读取其中的 QMUIAsset 并储存到 imagesAssetArray 中,传入后会赋值到 QMUIAssetsGroup,并自动刷新 UI 展示
- (void)refreshWithAssetsGroup:(QMUIAssetsGroup * _Nullable)assetsGroup;

@property(nullable, nonatomic, strong, readonly) NSMutableArray<QMUIAsset *> *imagesAssetArray;
@property(nullable, nonatomic, strong, readonly) QMUIAssetsGroup *assetsGroup;

/// 当前被选择的图片对应的 QMUIAsset 对象数组
@property(nullable, nonatomic, strong, readonly) NSMutableArray<QMUIAsset *> *selectedImageAssetArray;

/// 是否允许图片多选,默认为 YES。如果为 NO,则不显示 checkbox 和底部工具栏。
@property(nonatomic, assign) BOOL allowsMultipleSelection;

/// 最多可以选择的图片数,默认为无符号整形数的最大值,相当于没有限制
@property(nonatomic, assign) NSUInteger maximumSelectImageCount;

/// 最少需要选择的图片数,默认为 0
@property(nonatomic, assign) NSUInteger minimumSelectImageCount;

/// 选择图片超出最大图片限制时 alertView 的标题
@property(nullable, nonatomic, copy) NSString *alertTitleWhenExceedMaxSelectImageCount;

/// 选择图片超出最大图片限制时 alertView 底部按钮的标题
@property(nullable, nonatomic, copy) NSString *alertButtonTitleWhenExceedMaxSelectImageCount;

/**
 *  加载相册列表时会出现 loading,若需要自定义 loading 的形式,可将该属性置为 NO,默认为 YES。
 *  @see imagePickerViewControllerWillStartLoading: & imagePickerViewControllerDidFinishLoading:
 */
@property(nonatomic, assign) BOOL shouldShowDefaultLoadingView;

@end


@interface QMUIImagePickerViewController (UIAppearance)

+ (instancetype)appearance;

@end

NS_ASSUME_NONNULL_END


================================================
FILE: QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerViewController.m
================================================
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */

//
//  QMUIImagePickerViewController.m
//  qmui
//
//  Created by QMUI Team on 15/5/2.
//

#import "QMUIImagePickerViewController.h"
#import "QMUICore.h"
#import "QMUIImagePickerCollectionViewCell.h"
#import "QMUIButton.h"
#import "QMUINavigationButton.h"
#import "QMUIAssetsManager.h"
#import "QMUIAlertController.h"
#import "QMUIImagePickerHelper.h"
#import "QMUIImagePickerHelper.h"
#import "UICollectionView+QMUI.h"
#import "UIScrollView+QMUI.h"
#import "CALayer+QMUI.h"
#import "UIView+QMUI.h"
#import <MobileCoreServices/MobileCoreServices.h>
#import "QMUIEmptyView.h"
#import "UIViewController+QMUI.h"
#import "QMUILog.h"
#import "QMUIAppearance.h"

static NSString * const kVideoCellIdentifier = @"video";
static NSString * const kImageOrUnknownCellIdentifier = @"imageorunknown";


#pragma mark - QMUIImagePickerViewController (UIAppearance)

@implementation QMUIImagePickerViewController (UIAppearance)

+ (instancetype)appearance {
    return [QMUIAppearance appearanceForClass:self];
}

+ (void)initialize {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self initAppearance];
    });
}

+ (void)initAppearance {
    QMUIImagePickerViewController.appearance.minimumImageWidth = 75;
}

@end

#pragma mark - QMUIImagePickerViewController

@interface QMUIImagePickerViewController ()

@property(nonatomic, strong) QMUIImagePickerPreviewViewController *imagePickerPreviewViewController;
@property(nonatomic, assign) BOOL isImagesAssetLoaded;// 这个属性的作用描述:https://github.com/Tencent/QMUI_iOS/issues/219
@property(nonatomic, assign) BOOL hasScrollToInitialPosition;
@property(nonatomic, assign) BOOL canScrollToInitialPosition;// 要等数据加载完才允许滚动
@end

@implementation QMUIImagePickerViewController

- (void)didInitialize {
    [super didInitialize];
    
    [self qmui_applyAppearance];
    
    _allowsMultipleSelection = YES;
    _maximumSelectImageCount = INT_MAX;
    _minimumSelectImageCount = 0;
    _shouldShowDefaultLoadingView = YES;
}

- (void)dealloc {
    _collectionView.dataSource = nil;
    _collectionView.delegate = nil;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = UIColorWhite;
    [self.view addSubview:self.collectionView];
    if (self.allowsMultipleSelection) {
        [self.view addSubview:self.operationToolBarView];
    }
}

- (void)setupNavigationItems {
    [super setupNavigationItems];
    self.navigationItem.rightBarButtonItem = [UIBarButtonItem qmui_itemWithTitle:@"取消" target:self action:@selector(handleCancelPickerImage:)];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    // 由于被选中的图片 selectedImageAssetArray 是 property,所以可以由外部改变,
    // 因此 viewWillAppear 时检查一下图片被选中的情况,并刷新 collectionView
    if (self.allowsMultipleSelection) {
        // 只有允许多选,即底部工具栏显示时,需要重新设置底部工具栏的元素
        NSInteger selectedImageCount = [self.selectedImageAssetArray count];
        if (selectedImageCount > 0) {
            // 如果有图片被选择,则预览按钮和发送按钮可点击,并刷新当前被选中的图片数量
            self.previewButton.enabled = YES;
            self.sendButton.enabled = YES;
            self.imageCountLabel.text = [NSString stringWithFormat:@"%@", @(selectedImageCount)];
            self.imageCountLabel.hidden = NO;
        } else {
            // 如果没有任何图片被选择,则预览和发送按钮不可点击,并且隐藏显示图片数量的 Label
            self.previewButton.enabled = NO;
            self.sendButton.enabled = NO;
            self.imageCountLabel.hidden = YES;
        }
    }
    [self.collectionView reloadData];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    
    // 在 pop 回相簿列表时重置标志位以使下次进来 picker 时 collection 可以滚动到正确的初始位置
    // 但不能影响从 picker 进入大图的路径
    if (self.navigationController && ![self.navigationController.viewControllers containsObject:self]) {
        self.hasScrollToInitialPosition = NO;
    }
}

- (void)showEmptyView {
    [super showEmptyView];
    self.emptyView.backgroundColor = self.view.backgroundColor; // 为了盖住背后的 collectionView,这里加个背景色(不盖住的话会看到 collectionView 先滚到列表顶部然后跳到列表底部)
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    
    CGFloat operationToolBarViewHeight = 0;
    if (self.allowsMultipleSelection) {
        operationToolBarViewHeight = ToolBarHeight;
        CGFloat toolbarPaddingHorizontal = 12;
        self.operationToolBarView.frame = CGRectMake(0, CGRectGetHeight(self.view.bounds) - operationToolBarViewHeight, CGRectGetWidth(self.view.bounds), operationToolBarViewHeight);
        self.previewButton.frame = CGRectSetXY(self.previewButton.frame, toolbarPaddingHorizontal, CGFloatGetCenter(CGRectGetHeight(self.operationToolBarView.bounds) - SafeAreaInsetsConstantForDeviceWithNotch.bottom, CGRectGetHeight(self.previewButton.frame)));
        self.sendButton.frame = CGRectMake(CGRectGetWidth(self.operationToolBarView.bounds) - toolbarPaddingHorizontal - CGRectGetWidth(self.sendButton.frame), CGFloatGetCenter(CGRectGetHeight(self.operationToolBarView.frame) - SafeAreaInsetsConstantForDeviceWithNotch.bottom, CGRectGetHeight(self.sendButton.frame)), CGRectGetWidth(self.sendButton.frame), CGRectGetHeight(self.sendButton.frame));
        CGSize imageCountLabelSize = CGSizeMake(18, 18);
        self.imageCountLabel.frame = CGRectMake(CGRectGetMinX(self.sendButton.frame) - imageCountLabelSize.width - 5, CGRectGetMinY(self.sendButton.frame) + CGFloatGetCenter(CGRectGetHeight(self.sendButton.frame), imageCountLabelSize.height), imageCountLabelSize.width, imageCountLabelSize.height);
        self.imageCountLabel.layer.cornerRadius = CGRectGetHeight(self.imageCountLabel.bounds) / 2;
        operationToolBarViewHeight = CGRectGetHeight(self.operationToolBarView.frame);
    }
    
    if (!CGSizeEqualToSize(self.collectionView.frame.size, self.view.bounds.size)) {
        self.collectionView.frame = self.view.bounds;
    }
    UIEdgeInsets contentInset = UIEdgeInsetsMake(self.qmui_navigationBarMaxYInViewCoordinator, self.collectionView.safeAreaInsets.left, MAX(operationToolBarViewHeight, self.collectionView.safeAreaInsets.bottom), self.collectionView.safeAreaInsets.right);
    if (!UIEdgeInsetsEqualToEdgeInsets(self.collectionView.contentInset, contentInset)) {
        self.collectionView.contentInset = contentInset;
        self.collectionView.scrollIndicatorInsets = UIEdgeInsetsMake(contentInset.top, 0, contentInset.bottom, 0);
        // 放在这里是因为有时候会先走完 refreshWithAssetsGroup 里的 completion 再走到这里,此时前者不会导致 scollToInitialPosition 的滚动,所以在这里再调用一次保证一定会滚
        [self scrollToInitialPositionIfNeeded];
    }
}

- (void)refreshWithAssetsGroup:(QMUIAssetsGroup *)assetsGroup {
    _assetsGroup = assetsGroup;
    if (!self.imagesAssetArray) {
        _imagesAssetArray = [[NSMutableArray alloc] init];
        _selectedImageAssetArray = [[NSMutableArray alloc] init];
    } else {
        [self.imagesAssetArray removeAllObjects];
        // 这里不用 remove 选中的图片,因为支持跨相簿选图
//        [self.selectedImageAssetArray removeAllObjects];
    }
    // 通过 QMUIAssetsGroup 获取该相册所有的图片 QMUIAsset,并且储存到数组中
    QMUIAlbumSortType albumSortType = QMUIAlbumSortTypePositive;
    // 从 delegate 中获取相册内容的排序方式,如果没有实现这个 delegate,则使用 QMUIAlbumSortType 的默认值,即最新的内容排在最后面
    if (self.imagePickerViewControllerDelegate && [self.imagePickerViewControllerDelegate respondsToSelector:@selector(albumSortTypeForImagePickerViewController:)]) {
        albumSortType = [self.imagePickerViewControllerDelegate albumSortTypeForImagePickerViewController:self];
    }
    // 遍历相册内的资源较为耗时,交给子线程去处理,因此这里需要显示 Loading
    if ([self.imagePickerViewControllerDelegate respondsToSelector:@selector(imagePickerViewControllerWillStartLoading:)]) {
        [self.imagePickerViewControllerDelegate imagePickerViewControllerWillStartLoading:self];
    }
    if (self.shouldShowDefaultLoadingView) {
        [self showEmptyViewWithLoading];
    }
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [assetsGroup enumerateAssetsWithOptions:albumSortType usingBlock:^(QMUIAsset *resultAsset) {
            // 这里需要对 UI 进行操作,因此放回主线程处理
            dispatch_async(dispatch_get_main_queue(), ^{
                if (resultAsset) {
                    self.isImagesAssetLoaded = NO;
                    [self.imagesAssetArray addObject:resultAsset];
                } else {
                    // result 为 nil,即遍历相片或视频完毕
                    self.isImagesAssetLoaded = YES;// 这个属性的作用描述: https://github.com/Tencent/QMUI_iOS/issues/219
                    [self.collectionView reloadData];
                    [self.collectionView performBatchUpdates:^{
                    } completion:^(BOOL finished) {
                        [self scrollToInitialPositionIfNeeded];
                        if (self.shouldShowDefaultLoadingView) {
                          [self hideEmptyView];
                        }
                        if ([self.imagePickerViewControllerDelegate respondsToSelector:@selector(imagePickerViewControllerDidFinishLoading:)]) {
                            [self.imagePickerViewControllerDelegate imagePickerViewControllerDidFinishLoading:self];
                        }
                    }];
                }
            });
        }];
    });
}

- (void)initPreviewViewControllerIfNeeded {
    if (!self.imagePickerPreviewViewController) {
        self.imagePickerPreviewViewController = [self.imagePickerViewControllerDelegate imagePickerPreviewViewControllerForImagePickerViewController:self];
        self.imagePickerPreviewViewController.maximumSelectImageCount = self.maximumSelectImageCount;
        self.imagePickerPreviewViewController.minimumSelectImageCount = self.minimumSelectImageCount;
    }
}

- (CGSize)referenceImageSize {
    CGFloat collectionViewWidth = CGRectGetWidth(self.collectionView.bounds);
    CGFloat collectionViewContentSpacing = collectionViewWidth - UIEdgeInsetsGetHorizontalValue(self.collectionView.contentInset) - UIEdgeInsetsGetHorizontalValue(self.collectionViewLayout.sectionInset);
    NSInteger columnCount = floor(collectionViewContentSpacing / self.minimumImageWidth);
    CGFloat referenceImageWidth = self.minimumImageWidth;
    BOOL isSpacingEnoughWhenDisplayInMinImageSize = (self.minimumImageWidth + self.collectionViewLayout.minimumInteritemSpacing) * columnCount - self.collectionViewLayout.minimumInteritemSpacing <= collectionViewContentSpacing;
    if (!isSpacingEnoughWhenDisplayInMinImageSize) {
        // 算上图片之间的间隙后发现其实还是放不下啦,所以得把列数减少,然后放大图片以撑满剩余空间
        columnCount -= 1;
    }
    referenceImageWidth = floor((collectionViewContentSpacing - self.collectionViewLayout.minimumInteritemSpacing * (columnCount - 1)) / columnCount);
    return CGSizeMake(referenceImageWidth, referenceImageWidth);
}

- (void)setMinimumImageWidth:(CGFloat)minimumImageWidth {
    _minimumImageWidth = minimumImageWidth;
    [self referenceImageSize];
    [self.collectionView.collectionViewLayout invalidateLayout];
}

- (void)scrollToInitialPositionIfNeeded {
    if (_collectionView.qmui_visible && self.isImagesAssetLoaded && !self.hasScrollToInitialPosition) {
        if ([self.imagePickerViewControllerDelegate respondsToSelector:@selector(albumSortTypeForImagePickerViewController:)] && [self.imagePickerViewControllerDelegate albumSortTypeForImagePickerViewController:self] == QMUIAlbumSortTypeReverse) {
            [_collectionView qmui_scrollToTop];
        } else {
            [_collectionView qmui_scrollToBottom];
        }
        self.hasScrollToInitialPosition = YES;
    }
}

#pragma mark - Getters & Setters

@synthesize collectionViewLayout = _collectionViewLayout;
- (UICollectionViewFlowLayout *)collectionViewLayout {
    if (!_collectionViewLayout) {
        _collectionViewLayout = [[UICollectionViewFlowLayout alloc] init];
        CGFloat inset = PixelOne * 2; // no why, just beautiful
        _collectionViewLayout.sectionInset = UIEdgeInsetsMake(inset, inset, inset, inset);
        _collectionViewLayout.minimumLineSpacing = _collectionViewLayout.sectionInset.bottom;
        _collectionViewLayout.minimumInteritemSpacing = _collectionViewLayout.sectionInset.left;
    }
    return _collectionViewLayout;
}

@synthesize collectionView = _collectionView;
- (UICollectionView *)collectionView {
    if (!_collectionView) {
        _collectionView = [[UICollectionView alloc] initWithFrame:self.isViewLoaded ? self.view.bounds : CGRectZero collectionViewLayout:self.collectionViewLayout];
        _collectionView.delegate = self;
        _collectionView.dataSource = self;
        _collectionView.showsHorizontalScrollIndicator = NO;
        _collectionView.alwaysBounceHorizontal = NO;
        _collectionView.backgroundColor = UIColorClear;
        [_collectionView registerClass:[QMUIImagePickerCollectionViewCell class] forCellWithReuseIdentifier:kVideoCellIdentifier];
        [_collectionView registerClass:[QMUIImagePickerCollectionViewCell class] forCellWithReuseIdentifier:kImageOrUnknownCellIdentifier];
        _collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
    }
    return _collectionView;
}

@synthesize operationToolBarView = _operationToolBarView;
- (UIView *)operationToolBarView {
    if (!_operationToolBarView) {
        _operationToolBarView = [[UIView alloc] init];
        _operationToolBarView.backgroundColor = UIColorWhite;
        _operationToolBarView.qmui_borderPosition = QMUIViewBorderPositionTop;
        
        [_operationToolBarView addSubview:self.sendButton];
        [_operationToolBarView addSubview:self.previewButton];
        [_operationToolBarView addSubview:self.imageCountLabel];
    }
    return _operationToolBarView;
}

@synthesize sendButton = _sendButton;
- (QMUIButton *)sendButton {
    if (!_sendButton) {
        _sendButton = [[QMUIButton alloc] init];
        _sendButton.enabled = NO;
        _sendButton.titleLabel.font = UIFontMake(16);
        _sendButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight;
        [_sendButton setTitleColor:UIColorMake(124, 124, 124) forState:UIControlStateNormal];
        [_sendButton setTitleColor:UIColorGray forState:UIControlStateDisabled];
        [_sendButton setTitle:@"发送" forState:UIControlStateNormal];
        _sendButton.qmui_outsideEdge = UIEdgeInsetsMake(-12, -20, -12, -20);
        [_sendButton sizeToFit];
        [_sendButton addTarget:self action:@selector(handleSendButtonClick:) forControlEvents:UIControlEventTouchUpInside];
    }
    return _sendButton;
}

@synthesize previewButton = _previewButton;
- (QMUIButton *)previewButton {
    if (!_previewButton) {
        _previewButton = [[QMUIButton alloc] init];
        _previewButton.enabled = NO;
        _previewButton.titleLabel.font = self.sendButton.titleLabel.font;
        [_previewButton setTitleColor:[self.sendButton titleColorForState:UIControlStateNormal] forState:UIControlStateNormal];
        [_previewButton setTitleColor:[self.sendButton titleColorForState:UIControlStateDisabled] forState:UIControlStateDisabled];
        [_previewButton setTitle:@"预览" forState:UIControlStateNormal];
        _previewButton.qmui_outsideEdge = UIEdgeInsetsMake(-12, -20, -12, -20);
        [_previewButton sizeToFit];
        [_previewButton addTarget:self action:@selector(handlePreviewButtonClick:) forControlEvents:UIControlEventTouchUpInside];
    }
    return _previewButton;
}

@synthesize imageCountLabel = _imageCountLabel;
- (UILabel *)imageCountLabel {
    if (!_imageCountLabel) {
        _imageCountLabel = [[UILabel alloc] init];
        _imageCountLabel.userInteractionEnabled = NO;// 不要影响 sendButton 的事件
        _imageCountLabel.backgroundColor = ButtonTintColor;
        _imageCountLabel.textColor = UIColorWhite;
        _imageCountLabel.font = UIFontMake(12);
        _imageCountLabel.textAlignment = NSTextAlignmentCenter;
        _imageCountLabel.lineBreakMode = NSLineBreakByCharWrapping;
        _imageCountLabel.layer.masksToBounds = YES;
        _imageCountLabel.hidden = YES;
    }
    return _imageCountLabel;
}

- (void)setAllowsMultipleSelection:(BOOL)allowsMultipleSelection {
    _allowsMultipleSelection = allowsMultipleSelection;
    if (self.isViewLoaded) {
        if (_allowsMultipleSelection) {
            [self.view addSubview:self.operationToolBarView];
        } else {
            [_operationToolBarView removeFromSuperview];
        }
    }
}

#pragma mark - <UICollectionViewDelegate, UICollectionViewDataSource>

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return 1;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return [self.imagesAssetArray count];
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NS
Download .txt
gitextract_c6ylhudb/

├── .github/
│   └── ISSUE_TEMPLATE/
│       ├── ----.md
│       ├── bug_report.md
│       └── feature_request.md
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE.TXT
├── QMUIConfigurationTemplate/
│   ├── QMUIConfigurationTemplate.h
│   └── QMUIConfigurationTemplate.m
├── QMUIKit/
│   ├── Info.plist
│   ├── PrivacyInfo.xcprivacy
│   ├── QMUIComponents/
│   │   ├── AssetLibrary/
│   │   │   ├── QMUIAsset.h
│   │   │   ├── QMUIAsset.m
│   │   │   ├── QMUIAssetsGroup.h
│   │   │   ├── QMUIAssetsGroup.m
│   │   │   ├── QMUIAssetsManager.h
│   │   │   └── QMUIAssetsManager.m
│   │   ├── CAAnimation+QMUI.h
│   │   ├── CAAnimation+QMUI.m
│   │   ├── CALayer+QMUIViewAnimation.h
│   │   ├── CALayer+QMUIViewAnimation.m
│   │   ├── ImagePickerLibrary/
│   │   │   ├── QMUIAlbumViewController.h
│   │   │   ├── QMUIAlbumViewController.m
│   │   │   ├── QMUIImagePickerCollectionViewCell.h
│   │   │   ├── QMUIImagePickerCollectionViewCell.m
│   │   │   ├── QMUIImagePickerHelper.h
│   │   │   ├── QMUIImagePickerHelper.m
│   │   │   ├── QMUIImagePickerPreviewViewController.h
│   │   │   ├── QMUIImagePickerPreviewViewController.m
│   │   │   ├── QMUIImagePickerViewController.h
│   │   │   └── QMUIImagePickerViewController.m
│   │   ├── NavigationBarTransition/
│   │   │   ├── UINavigationBar+Transition.h
│   │   │   ├── UINavigationBar+Transition.m
│   │   │   ├── UINavigationController+NavigationBarTransition.h
│   │   │   └── UINavigationController+NavigationBarTransition.m
│   │   ├── QMUIAlertController.h
│   │   ├── QMUIAlertController.m
│   │   ├── QMUIAnimation/
│   │   │   ├── QMUIAnimationHelper.h
│   │   │   ├── QMUIAnimationHelper.m
│   │   │   ├── QMUIDisplayLinkAnimation.h
│   │   │   ├── QMUIDisplayLinkAnimation.m
│   │   │   └── QMUIEasings.h
│   │   ├── QMUIAppearance.h
│   │   ├── QMUIAppearance.m
│   │   ├── QMUIBadge/
│   │   │   ├── QMUIBadgeLabel.h
│   │   │   ├── QMUIBadgeLabel.m
│   │   │   ├── QMUIBadgeProtocol.h
│   │   │   ├── UIBarItem+QMUIBadge.h
│   │   │   ├── UIBarItem+QMUIBadge.m
│   │   │   ├── UIView+QMUIBadge.h
│   │   │   └── UIView+QMUIBadge.m
│   │   ├── QMUIButton/
│   │   │   ├── QMUIButton.h
│   │   │   ├── QMUIButton.m
│   │   │   ├── QMUINavigationButton.h
│   │   │   ├── QMUINavigationButton.m
│   │   │   ├── QMUIToolbarButton.h
│   │   │   └── QMUIToolbarButton.m
│   │   ├── QMUICellHeightCache.h
│   │   ├── QMUICellHeightCache.m
│   │   ├── QMUICellHeightKeyCache/
│   │   │   ├── QMUICellHeightKeyCache.h
│   │   │   ├── QMUICellHeightKeyCache.m
│   │   │   ├── UITableView+QMUICellHeightKeyCache.h
│   │   │   └── UITableView+QMUICellHeightKeyCache.m
│   │   ├── QMUICellSizeKeyCache/
│   │   │   ├── QMUICellSizeKeyCache.h
│   │   │   ├── QMUICellSizeKeyCache.m
│   │   │   ├── UICollectionView+QMUICellSizeKeyCache.h
│   │   │   └── UICollectionView+QMUICellSizeKeyCache.m
│   │   ├── QMUICheckbox.h
│   │   ├── QMUICheckbox.m
│   │   ├── QMUICollectionViewPagingLayout.h
│   │   ├── QMUICollectionViewPagingLayout.m
│   │   ├── QMUIConsole/
│   │   │   ├── QMUIConsole.h
│   │   │   ├── QMUIConsole.m
│   │   │   ├── QMUIConsoleToolbar.h
│   │   │   ├── QMUIConsoleToolbar.m
│   │   │   ├── QMUIConsoleViewController.h
│   │   │   ├── QMUIConsoleViewController.m
│   │   │   ├── QMUILog+QMUIConsole.h
│   │   │   └── QMUILog+QMUIConsole.m
│   │   ├── QMUIDialogViewController.h
│   │   ├── QMUIDialogViewController.m
│   │   ├── QMUIEmotionInputManager.h
│   │   ├── QMUIEmotionInputManager.m
│   │   ├── QMUIEmotionView.h
│   │   ├── QMUIEmotionView.m
│   │   ├── QMUIEmptyView.h
│   │   ├── QMUIEmptyView.m
│   │   ├── QMUIFloatLayoutView.h
│   │   ├── QMUIFloatLayoutView.m
│   │   ├── QMUIGridView.h
│   │   ├── QMUIGridView.m
│   │   ├── QMUIImagePreviewView/
│   │   │   ├── QMUIImagePreviewView.h
│   │   │   ├── QMUIImagePreviewView.m
│   │   │   ├── QMUIImagePreviewViewController.h
│   │   │   ├── QMUIImagePreviewViewController.m
│   │   │   ├── QMUIImagePreviewViewTransitionAnimator.h
│   │   │   └── QMUIImagePreviewViewTransitionAnimator.m
│   │   ├── QMUIKeyboardManager.h
│   │   ├── QMUIKeyboardManager.m
│   │   ├── QMUILabel.h
│   │   ├── QMUILabel.m
│   │   ├── QMUILayouter/
│   │   │   ├── QMUILayouter.h
│   │   │   ├── QMUILayouterItem.h
│   │   │   ├── QMUILayouterItem.m
│   │   │   ├── QMUILayouterLinearHorizontal.h
│   │   │   ├── QMUILayouterLinearHorizontal.m
│   │   │   ├── QMUILayouterLinearVertical.h
│   │   │   └── QMUILayouterLinearVertical.m
│   │   ├── QMUILog/
│   │   │   ├── QMUILog.h
│   │   │   ├── QMUILogItem.h
│   │   │   ├── QMUILogItem.m
│   │   │   ├── QMUILogNameManager.h
│   │   │   ├── QMUILogNameManager.m
│   │   │   ├── QMUILogger.h
│   │   │   └── QMUILogger.m
│   │   ├── QMUILogManagerViewController.h
│   │   ├── QMUILogManagerViewController.m
│   │   ├── QMUILogger+QMUIConfigurationTemplate.h
│   │   ├── QMUILogger+QMUIConfigurationTemplate.m
│   │   ├── QMUIMarqueeLabel.h
│   │   ├── QMUIMarqueeLabel.m
│   │   ├── QMUIModalPresentationViewController.h
│   │   ├── QMUIModalPresentationViewController.m
│   │   ├── QMUIMoreOperationController.h
│   │   ├── QMUIMoreOperationController.m
│   │   ├── QMUIMultipleDelegates/
│   │   │   ├── NSObject+QMUIMultipleDelegates.h
│   │   │   ├── NSObject+QMUIMultipleDelegates.m
│   │   │   ├── QMUIMultipleDelegates.h
│   │   │   └── QMUIMultipleDelegates.m
│   │   ├── QMUINavigationTitleView.h
│   │   ├── QMUINavigationTitleView.m
│   │   ├── QMUIOrderedDictionary.h
│   │   ├── QMUIOrderedDictionary.m
│   │   ├── QMUIPieProgressView.h
│   │   ├── QMUIPieProgressView.m
│   │   ├── QMUIPopupContainerView.h
│   │   ├── QMUIPopupContainerView.m
│   │   ├── QMUIPopupMenuView/
│   │   │   ├── QMUIPopupMenuItem.h
│   │   │   ├── QMUIPopupMenuItem.m
│   │   │   ├── QMUIPopupMenuItemView.h
│   │   │   ├── QMUIPopupMenuItemView.m
│   │   │   ├── QMUIPopupMenuItemViewProtocol.h
│   │   │   ├── QMUIPopupMenuView.h
│   │   │   └── QMUIPopupMenuView.m
│   │   ├── QMUIScrollAnimator/
│   │   │   ├── QMUINavigationBarScrollingAnimator.h
│   │   │   ├── QMUINavigationBarScrollingAnimator.m
│   │   │   ├── QMUINavigationBarScrollingSnapAnimator.h
│   │   │   ├── QMUINavigationBarScrollingSnapAnimator.m
│   │   │   ├── QMUIScrollAnimator.h
│   │   │   └── QMUIScrollAnimator.m
│   │   ├── QMUISearchBar.h
│   │   ├── QMUISearchBar.m
│   │   ├── QMUISearchController.h
│   │   ├── QMUISearchController.m
│   │   ├── QMUISegmentedControl.h
│   │   ├── QMUISegmentedControl.m
│   │   ├── QMUISheetPresentation/
│   │   │   ├── QMUISheetPresentationNavigationBar.h
│   │   │   ├── QMUISheetPresentationNavigationBar.m
│   │   │   ├── QMUISheetPresentationSupports.h
│   │   │   └── QMUISheetPresentationSupports.m
│   │   ├── QMUITableView.h
│   │   ├── QMUITableView.m
│   │   ├── QMUITableViewCell.h
│   │   ├── QMUITableViewCell.m
│   │   ├── QMUITableViewHeaderFooterView.h
│   │   ├── QMUITableViewHeaderFooterView.m
│   │   ├── QMUITableViewProtocols.h
│   │   ├── QMUITestView.h
│   │   ├── QMUITestView.m
│   │   ├── QMUITextField.h
│   │   ├── QMUITextField.m
│   │   ├── QMUITextView.h
│   │   ├── QMUITextView.m
│   │   ├── QMUITheme/
│   │   │   ├── QMUITheme.h
│   │   │   ├── QMUIThemeManager.h
│   │   │   ├── QMUIThemeManager.m
│   │   │   ├── QMUIThemeManagerCenter.h
│   │   │   ├── QMUIThemeManagerCenter.m
│   │   │   ├── QMUIThemePrivate.h
│   │   │   ├── QMUIThemePrivate.m
│   │   │   ├── UIColor+QMUITheme.h
│   │   │   ├── UIColor+QMUITheme.m
│   │   │   ├── UIImage+QMUITheme.h
│   │   │   ├── UIImage+QMUITheme.m
│   │   │   ├── UIView+QMUITheme.h
│   │   │   ├── UIView+QMUITheme.m
│   │   │   ├── UIViewController+QMUITheme.h
│   │   │   ├── UIViewController+QMUITheme.m
│   │   │   ├── UIVisualEffect+QMUITheme.h
│   │   │   └── UIVisualEffect+QMUITheme.m
│   │   ├── QMUITips.h
│   │   ├── QMUITips.m
│   │   ├── QMUIWeakObjectContainer.h
│   │   ├── QMUIWeakObjectContainer.m
│   │   ├── QMUIWindowSizeMonitor.h
│   │   ├── QMUIWindowSizeMonitor.m
│   │   ├── QMUIZoomImageView.h
│   │   ├── QMUIZoomImageView.m
│   │   ├── StaticTableView/
│   │   │   ├── QMUIStaticTableViewCellData.h
│   │   │   ├── QMUIStaticTableViewCellData.m
│   │   │   ├── QMUIStaticTableViewCellDataSource.h
│   │   │   ├── QMUIStaticTableViewCellDataSource.m
│   │   │   ├── UITableView+QMUIStaticCell.h
│   │   │   └── UITableView+QMUIStaticCell.m
│   │   └── ToastView/
│   │       ├── QMUIToastAnimator.h
│   │       ├── QMUIToastAnimator.m
│   │       ├── QMUIToastBackgroundView.h
│   │       ├── QMUIToastBackgroundView.m
│   │       ├── QMUIToastContentView.h
│   │       ├── QMUIToastContentView.m
│   │       ├── QMUIToastView.h
│   │       └── QMUIToastView.m
│   ├── QMUICore/
│   │   ├── QMUICommonDefines.h
│   │   ├── QMUIConfiguration.h
│   │   ├── QMUIConfiguration.m
│   │   ├── QMUIConfigurationMacros.h
│   │   ├── QMUICore.h
│   │   ├── QMUIHelper.h
│   │   ├── QMUIHelper.m
│   │   ├── QMUILab.h
│   │   ├── QMUIRuntime.h
│   │   └── QMUIRuntime.m
│   ├── QMUIKit.h
│   ├── QMUIMainFrame/
│   │   ├── QMUICommonTableViewController.h
│   │   ├── QMUICommonTableViewController.m
│   │   ├── QMUICommonViewController.h
│   │   ├── QMUICommonViewController.m
│   │   ├── QMUINavigationController.h
│   │   ├── QMUINavigationController.m
│   │   ├── QMUITabBarViewController.h
│   │   └── QMUITabBarViewController.m
│   ├── QMUIResources/
│   │   └── Images.xcassets/
│   │       ├── Contents.json
│   │       ├── QMUI_checkbox16.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_checkbox16_checked.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_checkbox16_disabled.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_checkbox16_indeterminate.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_console_clear.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_console_filter.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_console_filter_selected.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_console_logo.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_emotion_delete.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_hiddenAlbum.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_icloud_download_fault.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_pickerImage_checkbox.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_pickerImage_checkbox_checked.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_pickerImage_favorite.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_pickerImage_video_mark.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_previewImage_checkbox.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_previewImage_checkbox_checked.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_tips_done.imageset/
│   │       │   └── Contents.json
│   │       ├── QMUI_tips_error.imageset/
│   │       │   └── Contents.json
│   │       └── QMUI_tips_info.imageset/
│   │           └── Contents.json
│   └── UIKitExtensions/
│       ├── CALayer+QMUI.h
│       ├── CALayer+QMUI.m
│       ├── NSArray+QMUI.h
│       ├── NSArray+QMUI.m
│       ├── NSAttributedString+QMUI.h
│       ├── NSAttributedString+QMUI.m
│       ├── NSCharacterSet+QMUI.h
│       ├── NSCharacterSet+QMUI.m
│       ├── NSDictionary+QMUI.h
│       ├── NSDictionary+QMUI.m
│       ├── NSMethodSignature+QMUI.h
│       ├── NSMethodSignature+QMUI.m
│       ├── NSNumber+QMUI.h
│       ├── NSNumber+QMUI.m
│       ├── NSObject+QMUI.h
│       ├── NSObject+QMUI.m
│       ├── NSParagraphStyle+QMUI.h
│       ├── NSParagraphStyle+QMUI.m
│       ├── NSPointerArray+QMUI.h
│       ├── NSPointerArray+QMUI.m
│       ├── NSRegularExpression+QMUI.h
│       ├── NSRegularExpression+QMUI.m
│       ├── NSShadow+QMUI.h
│       ├── NSShadow+QMUI.m
│       ├── NSString+QMUI.h
│       ├── NSString+QMUI.m
│       ├── NSURL+QMUI.h
│       ├── NSURL+QMUI.m
│       ├── QMUIBarProtocol/
│       │   ├── QMUIBarProtocol.h
│       │   ├── QMUIBarProtocolPrivate.h
│       │   ├── QMUIBarProtocolPrivate.m
│       │   ├── UINavigationBar+QMUIBarProtocol.h
│       │   ├── UINavigationBar+QMUIBarProtocol.m
│       │   ├── UITabBar+QMUIBarProtocol.h
│       │   └── UITabBar+QMUIBarProtocol.m
│       ├── QMUIStringPrivate.h
│       ├── QMUIStringPrivate.m
│       ├── UIActivityIndicatorView+QMUI.h
│       ├── UIActivityIndicatorView+QMUI.m
│       ├── UIApplication+QMUI.h
│       ├── UIApplication+QMUI.m
│       ├── UIBarItem+QMUI.h
│       ├── UIBarItem+QMUI.m
│       ├── UIBezierPath+QMUI.h
│       ├── UIBezierPath+QMUI.m
│       ├── UIBlurEffect+QMUI.h
│       ├── UIBlurEffect+QMUI.m
│       ├── UIButton+QMUI.h
│       ├── UIButton+QMUI.m
│       ├── UICollectionView+QMUI.h
│       ├── UICollectionView+QMUI.m
│       ├── UICollectionViewCell+QMUI.h
│       ├── UICollectionViewCell+QMUI.m
│       ├── UIColor+QMUI.h
│       ├── UIColor+QMUI.m
│       ├── UIControl+QMUI.h
│       ├── UIControl+QMUI.m
│       ├── UIFont+QMUI.h
│       ├── UIFont+QMUI.m
│       ├── UIGestureRecognizer+QMUI.h
│       ├── UIGestureRecognizer+QMUI.m
│       ├── UIImage+QMUI.h
│       ├── UIImage+QMUI.m
│       ├── UIImageView+QMUI.h
│       ├── UIImageView+QMUI.m
│       ├── UIInterface+QMUI.h
│       ├── UIInterface+QMUI.m
│       ├── UILabel+QMUI.h
│       ├── UILabel+QMUI.m
│       ├── UIMenuController+QMUI.h
│       ├── UIMenuController+QMUI.m
│       ├── UINavigationBar+QMUI.h
│       ├── UINavigationBar+QMUI.m
│       ├── UINavigationController+QMUI.h
│       ├── UINavigationController+QMUI.m
│       ├── UINavigationItem+QMUI.h
│       ├── UINavigationItem+QMUI.m
│       ├── UIScrollView+QMUI.h
│       ├── UIScrollView+QMUI.m
│       ├── UISearchBar+QMUI.h
│       ├── UISearchBar+QMUI.m
│       ├── UISearchController+QMUI.h
│       ├── UISearchController+QMUI.m
│       ├── UISlider+QMUI.h
│       ├── UISlider+QMUI.m
│       ├── UISwitch+QMUI.h
│       ├── UISwitch+QMUI.m
│       ├── UITabBar+QMUI.h
│       ├── UITabBar+QMUI.m
│       ├── UITabBarItem+QMUI.h
│       ├── UITabBarItem+QMUI.m
│       ├── UITableView+QMUI.h
│       ├── UITableView+QMUI.m
│       ├── UITableViewCell+QMUI.h
│       ├── UITableViewCell+QMUI.m
│       ├── UITableViewHeaderFooterView+QMUI.h
│       ├── UITableViewHeaderFooterView+QMUI.m
│       ├── UITextField+QMUI.h
│       ├── UITextField+QMUI.m
│       ├── UITextInputTraits+QMUI.h
│       ├── UITextInputTraits+QMUI.m
│       ├── UITextView+QMUI.h
│       ├── UITextView+QMUI.m
│       ├── UIToolbar+QMUI.h
│       ├── UIToolbar+QMUI.m
│       ├── UITraitCollection+QMUI.h
│       ├── UITraitCollection+QMUI.m
│       ├── UIView+QMUI.h
│       ├── UIView+QMUI.m
│       ├── UIView+QMUIBorder.h
│       ├── UIView+QMUIBorder.m
│       ├── UIViewController+QMUI.h
│       ├── UIViewController+QMUI.m
│       ├── UIVisualEffectView+QMUI.h
│       ├── UIVisualEffectView+QMUI.m
│       ├── UIWindow+QMUI.h
│       └── UIWindow+QMUI.m
├── QMUIKit.podspec
├── QMUIKitTests/
│   ├── Components/
│   │   └── QMUIThemeTests.m
│   ├── Core/
│   │   └── QMUICommonDefinesTests.m
│   ├── Info.plist
│   └── UIKitExtensions/
│       ├── NSObjectTests.m
│       ├── NSStringTests.m
│       ├── UIButtonTests.m
│       └── UIColorTests.m
├── README.md
├── add_license.py
├── new_license_content.txt
├── old_license_content.txt
├── qmui.xcodeproj/
│   ├── project.pbxproj
│   ├── project.xcworkspace/
│   │   ├── contents.xcworkspacedata
│   │   └── xcshareddata/
│   │       └── IDEWorkspaceChecks.plist
│   └── xcshareddata/
│       └── xcschemes/
│           ├── QMUIKit.xcscheme
│           └── QMUIKitTests.xcscheme
└── umbrellaHeaderFileCreator.py
Download .txt
SYMBOL INDEX (128 symbols across 23 files)

FILE: QMUIKit/QMUIComponents/AssetLibrary/QMUIAsset.h
  type QMUIAssetTypeUnknow (line 19) | typedef NS_ENUM(NSUInteger, QMUIAssetType) {
  type QMUIAssetSubTypeUnknow (line 26) | typedef NS_ENUM(NSUInteger, QMUIAssetSubType) {
  type QMUIAssetDownloadStatusSucceed (line 34) | typedef NS_ENUM(NSUInteger, QMUIAssetDownloadStatus) {

FILE: QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetsGroup.h
  type QMUIAlbumContentTypeAll (line 26) | typedef NS_ENUM(NSUInteger, QMUIAlbumContentType) {
  type QMUIAlbumSortTypePositive (line 34) | typedef NS_ENUM(NSUInteger, QMUIAlbumSortType) {

FILE: QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetsManager.h
  type QMUIAssetAuthorizationStatusNotDetermined (line 30) | typedef NS_ENUM(NSUInteger, QMUIAssetAuthorizationStatus) {

FILE: QMUIKit/QMUIComponents/QMUIAnimation/QMUIEasings.h
  function CG_INLINE (line 20) | CG_INLINE CGFloat
  function CG_INLINE (line 25) | CG_INLINE CGFloat
  function CG_INLINE (line 30) | CG_INLINE CGFloat
  function CG_INLINE (line 35) | CG_INLINE CGFloat
  function CG_INLINE (line 40) | CG_INLINE CGFloat
  function CG_INLINE (line 45) | CG_INLINE CGFloat
  function CG_INLINE (line 50) | CG_INLINE CGFloat
  function CG_INLINE (line 55) | CG_INLINE CGFloat
  function CG_INLINE (line 60) | CG_INLINE CGFloat
  function CG_INLINE (line 65) | CG_INLINE CGFloat
  function CG_INLINE (line 70) | CG_INLINE CGFloat
  function CG_INLINE (line 75) | CG_INLINE CGFloat
  function CG_INLINE (line 80) | CG_INLINE CGFloat
  function CG_INLINE (line 85) | CG_INLINE CGFloat
  function CG_INLINE (line 90) | CG_INLINE CGFloat
  function CG_INLINE (line 95) | CG_INLINE CGFloat
  function CG_INLINE (line 100) | CG_INLINE CGFloat
  function CG_INLINE (line 105) | CG_INLINE CGFloat
  function CG_INLINE (line 110) | CG_INLINE CGFloat
  function CG_INLINE (line 115) | CG_INLINE CGFloat
  function CG_INLINE (line 120) | CG_INLINE CGFloat
  function CG_INLINE (line 125) | CG_INLINE CGFloat
  function CG_INLINE (line 130) | CG_INLINE CGFloat
  function CG_INLINE (line 135) | CG_INLINE CGFloat
  function CG_INLINE (line 141) | CG_INLINE CGFloat
  function CG_INLINE (line 152) | CG_INLINE CGFloat
  function CG_INLINE (line 157) | CG_INLINE CGFloat
  function CG_INLINE (line 162) | CG_INLINE CGFloat
  function CG_INLINE (line 171) | CG_INLINE CGFloat
  function CG_INLINE (line 184) | CG_INLINE CGFloat
  function CG_INLINE (line 189) | CG_INLINE CGFloat
  function CG_INLINE (line 198) | CG_INLINE CGFloat

FILE: QMUIKit/QMUIComponents/QMUICollectionViewPagingLayout.h
  type QMUICollectionViewPagingLayoutStyleDefault (line 18) | typedef NS_ENUM(NSInteger, QMUICollectionViewPagingLayoutStyle) {

FILE: QMUIKit/QMUIComponents/QMUIImagePreviewView/QMUIImagePreviewView.h
  type QMUIImagePreviewMediaTypeImage (line 22) | typedef NS_ENUM (NSUInteger, QMUIImagePreviewMediaType) {

FILE: QMUIKit/QMUIComponents/QMUIImagePreviewView/QMUIImagePreviewViewController.h
  type QMUIImagePreviewViewControllerTransitioningStyleFade (line 24) | typedef NS_ENUM(NSUInteger, QMUIImagePreviewViewControllerTransitioningS...

FILE: QMUIKit/QMUIComponents/QMUIModalPresentationViewController.h
  type QMUIModalPresentationAnimationStyleFade (line 23) | typedef NS_ENUM(NSUInteger, QMUIModalPresentationAnimationStyle) {

FILE: QMUIKit/QMUIComponents/QMUINavigationTitleView.h
  type QMUINavigationTitleViewAccessoryTypeNone (line 50) | typedef NS_ENUM(NSInteger, QMUINavigationTitleViewAccessoryType) {

FILE: QMUIKit/QMUIComponents/QMUIPieProgressView.h
  type QMUIPieProgressViewShapeSector (line 28) | typedef NS_ENUM(NSUInteger, QMUIPieProgressViewShape) {

FILE: QMUIKit/QMUIComponents/QMUIPopupContainerView.h
  function NS_ASSUME_NONNULL_BEGIN (line 19) | NS_ASSUME_NONNULL_BEGIN

FILE: QMUIKit/QMUIComponents/QMUIPopupMenuView/QMUIPopupMenuView.h
  type QMUIPopupMenuSelectedLayoutAtEnd (line 32) | typedef NS_ENUM(NSInteger, QMUIPopupMenuSelectedLayout) {

FILE: QMUIKit/QMUIComponents/QMUITableViewHeaderFooterView.h
  type QMUITableViewHeaderFooterViewTypeUnknow (line 18) | typedef NS_ENUM(NSUInteger, QMUITableViewHeaderFooterViewType) {

FILE: QMUIKit/QMUIComponents/StaticTableView/QMUIStaticTableViewCellData.h
  type QMUIStaticTableViewCellAccessoryTypeNone (line 23) | typedef NS_ENUM(NSInteger, QMUIStaticTableViewCellAccessoryType) {

FILE: QMUIKit/QMUIComponents/ToastView/QMUIToastView.h
  type QMUIToastViewPositionTop (line 22) | typedef NS_ENUM(NSInteger, QMUIToastViewPosition) {

FILE: QMUIKit/QMUICore/QMUICommonDefines.h
  function CG_INLINE (line 302) | CG_INLINE void
  function CG_INLINE (line 307) | CG_INLINE void
  function CG_INLINE (line 330) | CG_INLINE SEL
  function CG_INLINE (line 347) | CG_INLINE CGFloat
  function CG_INLINE (line 357) | CG_INLINE CGFloat
  function CG_INLINE (line 380) | CG_INLINE CGFloat
  function CG_INLINE (line 388) | CG_INLINE CGFloat
  function CG_INLINE (line 395) | CG_INLINE BOOL
  function CG_INLINE (line 400) | CG_INLINE BOOL
  function CG_INLINE (line 413) | CG_INLINE CGFloat
  function CG_INLINE (line 458) | CG_INLINE NSInteger _RoundedIntegerFromCGFloat(CGFloat value, NSUInteger...
  function CG_INLINE (line 503) | CG_INLINE CGPoint
  function CG_INLINE (line 508) | CG_INLINE CGPoint
  function CG_INLINE (line 514) | CG_INLINE CGPoint
  function CG_INLINE (line 523) | CG_INLINE CGFloat
  function CG_INLINE (line 529) | CG_INLINE CGFloat
  function CG_INLINE (line 535) | CG_INLINE UIEdgeInsets
  function CG_INLINE (line 544) | CG_INLINE UIEdgeInsets
  function CG_INLINE (line 550) | CG_INLINE UIEdgeInsets
  function CG_INLINE (line 555) | CG_INLINE UIEdgeInsets
  function CG_INLINE (line 561) | CG_INLINE UIEdgeInsets
  function CG_INLINE (line 567) | CG_INLINE UIEdgeInsets
  function CG_INLINE (line 573) | CG_INLINE UIEdgeInsets
  function CG_INLINE (line 582) | CG_INLINE BOOL
  function CG_INLINE (line 588) | CG_INLINE BOOL
  function CG_INLINE (line 594) | CG_INLINE BOOL
  function CG_INLINE (line 600) | CG_INLINE BOOL
  function CG_INLINE (line 606) | CG_INLINE CGSize
  function CG_INLINE (line 612) | CG_INLINE CGSize
  function CG_INLINE (line 618) | CG_INLINE CGSize
  function CG_INLINE (line 623) | CG_INLINE CGSize
  function CG_INLINE (line 629) | CG_INLINE CGSize
  function CG_INLINE (line 638) | CG_INLINE BOOL
  function CG_INLINE (line 644) | CG_INLINE BOOL
  function CG_INLINE (line 650) | CG_INLINE BOOL
  function CG_INLINE (line 656) | CG_INLINE CGRect
  function CG_INLINE (line 662) | CG_INLINE CGRect
  function CG_INLINE (line 668) | CG_INLINE CGRect
  function CG_INLINE (line 674) | CG_INLINE CGPoint
  function CG_INLINE (line 685) | CG_INLINE CGRect
  function CG_INLINE (line 705) | CG_INLINE CGRect
  function CG_INLINE (line 711) | CG_INLINE CGFloat
  function CG_INLINE (line 717) | CG_INLINE CGFloat
  function CG_INLINE (line 723) | CG_INLINE CGFloat
  function CG_INLINE (line 729) | CG_INLINE CGFloat
  function CG_INLINE (line 735) | CG_INLINE CGRect
  function CG_INLINE (line 741) | CG_INLINE CGRect
  function CG_INLINE (line 746) | CG_INLINE CGRect
  function CG_INLINE (line 752) | CG_INLINE CGRect
  function CG_INLINE (line 758) | CG_INLINE CGRect
  function CG_INLINE (line 764) | CG_INLINE CGRect
  function CG_INLINE (line 771) | CG_INLINE CGRect
  function CG_INLINE (line 780) | CG_INLINE CGRect
  function CG_INLINE (line 789) | CG_INLINE CGRect
  function CG_INLINE (line 796) | CG_INLINE CGRect
  function CG_INLINE (line 802) | CG_INLINE CGRect
  function CG_INLINE (line 808) | CG_INLINE CGRect
  function CG_INLINE (line 815) | CG_INLINE CGRect
  function CG_INLINE (line 824) | CG_INLINE CGRect
  function CG_INLINE (line 833) | CG_INLINE CGRect
  function CG_INLINE (line 839) | CG_INLINE CGRect
  function CG_INLINE (line 848) | CG_INLINE CGRect
  function CG_INLINE (line 858) | CG_INLINE BOOL

FILE: QMUIKit/QMUICore/QMUIRuntime.h
  function interface (line 24) | interface QMUIPropertyDescriptor : NSObject
  function CG_INLINE (line 70) | CG_INLINE BOOL
  function CG_INLINE (line 95) | CG_INLINE BOOL
  function CG_INLINE (line 106) | CG_INLINE BOOL
  function CG_INLINE (line 151) | CG_INLINE BOOL
  type classref (line 325) | struct classref

FILE: QMUIKit/QMUIMainFrame/QMUICommonViewController.h
  function interface (line 40) | interface QMUICommonViewController : UIViewController {

FILE: QMUIKit/UIKitExtensions/UIFont+QMUI.h
  type QMUIFontWeightLight (line 36) | typedef NS_ENUM(NSUInteger, QMUIFontWeight) {

FILE: QMUIKit/UIKitExtensions/UIImage+QMUI.h
  type QMUIImageGradientTypeHorizontal (line 52) | typedef NS_ENUM(NSInteger, QMUIImageGradientType) {

FILE: QMUIKit/UIKitExtensions/UIScrollView+QMUI.h
  type QMUIScrollPositionNone (line 18) | typedef NS_ENUM(NSInteger, QMUIScrollPosition) {

FILE: QMUIKit/UIKitExtensions/UIView+QMUIBorder.h
  type QMUIViewBorderLocationInside (line 28) | typedef NS_ENUM(NSUInteger, QMUIViewBorderLocation) {

FILE: add_license.py
  function is_match_anyone_dir (line 47) | def is_match_anyone_dir(path, dir_list):
  function is_match_anyone_str (line 54) | def is_match_anyone_str(filename, str_list):
  function is_match_anyone_regex (line 61) | def is_match_anyone_regex(line, regex_list):
  function find_file (line 68) | def find_file(start, suffix, ignore_dirs, ignore_files):
  function add_comment (line 79) | def add_comment(rule):
Condensed preview — 386 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (3,210K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/----.md",
    "chars": 290,
    "preview": "---\nname: 使用方式\nabout: 咨询 QMUI 某些功能的用法\ntitle: ''\nlabels: help wanted\nassignees: ''\n\n---\n\nQMUI 已经提供了**详尽的注释文档**,以及**完整的示例项"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 326,
    "preview": "---\nname: Bug\nabout: QMUIKit 框架本身的 bug(请注意区分非 QMUIKit 代码引发的问题)\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Bug 表现**\n问题的具体"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 154,
    "preview": "---\nname: 意见与建议\nabout: 功能、接口设计等的相关建议\ntitle: ''\nlabels: suggest\nassignees: ''\n\n---\n\n**现存问题或期望目标**\n对于功能的建议,请说明具体的场景,现在的代码为"
  },
  {
    "path": ".gitignore",
    "chars": 24,
    "preview": ".DS_Store\nxcuserdata/\n\n\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 584,
    "preview": "[腾讯开源激励计划](https://opensource.tencent.com/contribution) 鼓励开发者的参与和贡献,期待你的加入。我们欢迎 [report Issues](https://github.com/QMUI/"
  },
  {
    "path": "LICENSE.TXT",
    "chars": 1707,
    "preview": "Tencent is pleased to support the open source community by making QMUI_iOS available.  \nCopyright (C) 2016-2021 THL A29 "
  },
  {
    "path": "QMUIConfigurationTemplate/QMUIConfigurationTemplate.h",
    "chars": 1403,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIConfigurationTemplate/QMUIConfigurationTemplate.m",
    "chars": 36317,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/Info.plist",
    "chars": 770,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "QMUIKit/PrivacyInfo.xcprivacy",
    "chars": 598,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "QMUIKit/QMUIComponents/AssetLibrary/QMUIAsset.h",
    "chars": 5649,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/AssetLibrary/QMUIAsset.m",
    "chars": 16381,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetsGroup.h",
    "chars": 3089,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetsGroup.m",
    "chars": 4074,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetsManager.h",
    "chars": 6308,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/AssetLibrary/QMUIAssetsManager.m",
    "chars": 20890,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/CAAnimation+QMUI.h",
    "chars": 1132,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/CAAnimation+QMUI.m",
    "chars": 3987,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/CALayer+QMUIViewAnimation.h",
    "chars": 1154,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/CALayer+QMUIViewAnimation.m",
    "chars": 4220,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIAlbumViewController.h",
    "chars": 3909,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIAlbumViewController.m",
    "chars": 11594,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerCollectionViewCell.h",
    "chars": 2876,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerCollectionViewCell.m",
    "chars": 9420,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerHelper.h",
    "chars": 2908,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerHelper.m",
    "chars": 6904,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerPreviewViewController.h",
    "chars": 4088,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerPreviewViewController.m",
    "chars": 20767,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerViewController.h",
    "chars": 6312,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerViewController.m",
    "chars": 28194,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationBar+Transition.h",
    "chars": 1390,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationBar+Transition.m",
    "chars": 13538,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationController+NavigationBarTransition.h",
    "chars": 1178,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationController+NavigationBarTransition.m",
    "chars": 32354,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIAlertController.h",
    "chars": 14543,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIAlertController.m",
    "chars": 62514,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIAnimation/QMUIAnimationHelper.h",
    "chars": 3741,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIAnimation/QMUIAnimationHelper.m",
    "chars": 11882,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIAnimation/QMUIDisplayLinkAnimation.h",
    "chars": 6025,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIAnimation/QMUIDisplayLinkAnimation.m",
    "chars": 11413,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIAnimation/QMUIEasings.h",
    "chars": 5700,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIAppearance.h",
    "chars": 2114,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIAppearance.m",
    "chars": 3419,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIBadge/QMUIBadgeLabel.h",
    "chars": 247,
    "preview": "//\n//  QMUIBadgeLabel.h\n//  QMUIKit\n//\n//  Created by molice on 2023/7/26.\n//  Copyright © 2023 QMUI Team. All rights re"
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIBadge/QMUIBadgeLabel.m",
    "chars": 1899,
    "preview": "//\n//  QMUIBadgeLabel.m\n//  QMUIKit\n//\n//  Created by molice on 2023/7/26.\n//  Copyright © 2023 QMUI Team. All rights re"
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIBadge/QMUIBadgeProtocol.h",
    "chars": 3777,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIBadge/UIBarItem+QMUIBadge.h",
    "chars": 1219,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIBadge/UIBarItem+QMUIBadge.m",
    "chars": 12682,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIBadge/UIView+QMUIBadge.h",
    "chars": 1132,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIBadge/UIView+QMUIBadge.m",
    "chars": 16934,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIButton/QMUIButton.h",
    "chars": 4685,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIButton/QMUIButton.m",
    "chars": 16527,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIButton/QMUINavigationButton.h",
    "chars": 3347,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIButton/QMUINavigationButton.m",
    "chars": 24242,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIButton/QMUIToolbarButton.h",
    "chars": 2179,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIButton/QMUIToolbarButton.m",
    "chars": 3939,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUICellHeightCache.h",
    "chars": 8895,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUICellHeightCache.m",
    "chars": 35917,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUICellHeightKeyCache/QMUICellHeightKeyCache.h",
    "chars": 1576,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUICellHeightKeyCache/QMUICellHeightKeyCache.m",
    "chars": 1881,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUICellHeightKeyCache/UITableView+QMUICellHeightKeyCache.h",
    "chars": 2687,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUICellHeightKeyCache/UITableView+QMUICellHeightKeyCache.m",
    "chars": 13662,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUICellSizeKeyCache/QMUICellSizeKeyCache.h",
    "chars": 1461,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUICellSizeKeyCache/QMUICellSizeKeyCache.m",
    "chars": 1803,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUICellSizeKeyCache/UICollectionView+QMUICellSizeKeyCache.h",
    "chars": 1479,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUICellSizeKeyCache/UICollectionView+QMUICellSizeKeyCache.m",
    "chars": 10526,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUICheckbox.h",
    "chars": 1157,
    "preview": "//\n//  QMUICheckbox.h\n//  QMUIKit\n//\n//  Created by molice on 2024/8/1.\n//  Copyright © 2024 QMUI Team. All rights reser"
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUICheckbox.m",
    "chars": 3744,
    "preview": "//\n//  QMUICheckbox.m\n//  QMUIKit\n//\n//  Created by molice on 2024/8/1.\n//  Copyright © 2024 QMUI Team. All rights reser"
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUICollectionViewPagingLayout.h",
    "chars": 2957,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUICollectionViewPagingLayout.m",
    "chars": 13593,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIConsole/QMUIConsole.h",
    "chars": 3061,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIConsole/QMUIConsole.m",
    "chars": 5658,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIConsole/QMUIConsoleToolbar.h",
    "chars": 1458,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIConsole/QMUIConsoleToolbar.m",
    "chars": 8764,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIConsole/QMUIConsoleViewController.h",
    "chars": 1530,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIConsole/QMUIConsoleViewController.m",
    "chars": 34748,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIConsole/QMUILog+QMUIConsole.h",
    "chars": 880,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIConsole/QMUILog+QMUIConsole.m",
    "chars": 2572,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIDialogViewController.h",
    "chars": 10501,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIDialogViewController.m",
    "chars": 40146,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIEmotionInputManager.h",
    "chars": 3492,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIEmotionInputManager.m",
    "chars": 7103,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIEmotionView.h",
    "chars": 5280,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIEmotionView.m",
    "chars": 29677,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIEmptyView.h",
    "chars": 3675,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIEmptyView.m",
    "chars": 13367,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIFloatLayoutView.h",
    "chars": 1799,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIFloatLayoutView.m",
    "chars": 5153,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIGridView.h",
    "chars": 1678,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIGridView.m",
    "chars": 6018,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIImagePreviewView/QMUIImagePreviewView.h",
    "chars": 3871,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIImagePreviewView/QMUIImagePreviewView.m",
    "chars": 13727,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIImagePreviewView/QMUIImagePreviewViewController.h",
    "chars": 3758,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIImagePreviewView/QMUIImagePreviewViewController.m",
    "chars": 11391,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIImagePreviewView/QMUIImagePreviewViewTransitionAnimator.h",
    "chars": 3768,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIImagePreviewView/QMUIImagePreviewViewTransitionAnimator.m",
    "chars": 14411,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIKeyboardManager.h",
    "chars": 8709,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIKeyboardManager.m",
    "chars": 47343,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUILabel.h",
    "chars": 2050,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUILabel.m",
    "chars": 8073,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUILayouter/QMUILayouter.h",
    "chars": 902,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUILayouter/QMUILayouterItem.h",
    "chars": 5597,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUILayouter/QMUILayouterItem.m",
    "chars": 9903,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUILayouter/QMUILayouterLinearHorizontal.h",
    "chars": 1875,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUILayouter/QMUILayouterLinearHorizontal.m",
    "chars": 10426,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUILayouter/QMUILayouterLinearVertical.h",
    "chars": 1871,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUILayouter/QMUILayouterLinearVertical.m",
    "chars": 10470,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUILog/QMUILog.h",
    "chars": 2264,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUILog/QMUILogItem.h",
    "chars": 1819,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUILog/QMUILogItem.m",
    "chars": 2143,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUILog/QMUILogNameManager.h",
    "chars": 1565,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUILog/QMUILogNameManager.m",
    "chars": 3955,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUILog/QMUILogger.h",
    "chars": 2134,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUILog/QMUILogger.m",
    "chars": 2366,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUILogManagerViewController.h",
    "chars": 1491,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUILogManagerViewController.m",
    "chars": 11333,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUILogger+QMUIConfigurationTemplate.h",
    "chars": 900,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUILogger+QMUIConfigurationTemplate.m",
    "chars": 2022,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIMarqueeLabel.h",
    "chars": 3309,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIMarqueeLabel.m",
    "chars": 11324,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIModalPresentationViewController.h",
    "chars": 13160,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m",
    "chars": 33577,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIMoreOperationController.h",
    "chars": 8068,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIMoreOperationController.m",
    "chars": 33410,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIMultipleDelegates/NSObject+QMUIMultipleDelegates.h",
    "chars": 2135,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIMultipleDelegates/NSObject+QMUIMultipleDelegates.m",
    "chars": 7393,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIMultipleDelegates/QMUIMultipleDelegates.h",
    "chars": 1374,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIMultipleDelegates/QMUIMultipleDelegates.m",
    "chars": 8212,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUINavigationTitleView.h",
    "chars": 6645,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUINavigationTitleView.m",
    "chars": 37972,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIOrderedDictionary.h",
    "chars": 2059,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIOrderedDictionary.m",
    "chars": 5151,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIPieProgressView.h",
    "chars": 1915,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIPieProgressView.m",
    "chars": 5951,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIPopupContainerView.h",
    "chars": 11191,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIPopupContainerView.m",
    "chars": 51522,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIPopupMenuView/QMUIPopupMenuItem.h",
    "chars": 2534,
    "preview": "//\n//  QMUIPopupMenuItem.h\n//  QMUIKit\n//\n//  Created by molice on 2024/6/17.\n//  Copyright © 2024 QMUI Team. All rights"
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIPopupMenuView/QMUIPopupMenuItem.m",
    "chars": 1362,
    "preview": "//\n//  QMUIPopupMenuItem.m\n//  QMUIKit\n//\n//  Created by molice on 2024/6/17.\n//  Copyright © 2024 QMUI Team. All rights"
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIPopupMenuView/QMUIPopupMenuItemView.h",
    "chars": 911,
    "preview": "//\n//  QMUIPopupMenuItemView.h\n//  QMUIKit\n//\n//  Created by molice on 2024/6/17.\n//  Copyright © 2024 QMUI Team. All ri"
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIPopupMenuView/QMUIPopupMenuItemView.m",
    "chars": 6737,
    "preview": "//\n//  QMUIPopupMenuItemView.m\n//  QMUIKit\n//\n//  Created by molice on 2024/6/17.\n//  Copyright © 2024 QMUI Team. All ri"
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIPopupMenuView/QMUIPopupMenuItemViewProtocol.h",
    "chars": 485,
    "preview": "//\n//  QMUIPopupMenuItemViewProtocol.h\n//  QMUIKit\n//\n//  Created by molice on 2024/6/17.\n//  Copyright © 2024 QMUI Team"
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIPopupMenuView/QMUIPopupMenuView.h",
    "chars": 7732,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIPopupMenuView/QMUIPopupMenuView.m",
    "chars": 24157,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIScrollAnimator/QMUINavigationBarScrollingAnimator.h",
    "chars": 4980,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIScrollAnimator/QMUINavigationBarScrollingAnimator.m",
    "chars": 5534,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIScrollAnimator/QMUINavigationBarScrollingSnapAnimator.h",
    "chars": 2470,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIScrollAnimator/QMUINavigationBarScrollingSnapAnimator.m",
    "chars": 3678,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIScrollAnimator/QMUIScrollAnimator.h",
    "chars": 1488,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIScrollAnimator/QMUIScrollAnimator.m",
    "chars": 2290,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUISearchBar.h",
    "chars": 830,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUISearchBar.m",
    "chars": 1227,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUISearchController.h",
    "chars": 6658,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUISearchController.m",
    "chars": 24699,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUISegmentedControl.h",
    "chars": 2605,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUISegmentedControl.m",
    "chars": 5907,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUISheetPresentation/QMUISheetPresentationNavigationBar.h",
    "chars": 467,
    "preview": "//\n//  QMUISheetPresentationNavigationBar.h\n//  QMUIKit\n//\n//  Created by molice on 2024/2/27.\n//  Copyright © 2024 QMUI"
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUISheetPresentation/QMUISheetPresentationNavigationBar.m",
    "chars": 1734,
    "preview": "//\n//  QMUISheetPresentationNavigationBar.m\n//  QMUIKit\n//\n//  Created by molice on 2024/2/27.\n//  Copyright © 2024 QMUI"
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUISheetPresentation/QMUISheetPresentationSupports.h",
    "chars": 3056,
    "preview": "//\n//  QMUISheetPresentationSupports.h\n//  QMUIKit\n//\n//  Created by molice on 2024/2/27.\n//  Copyright © 2024 QMUI Team"
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUISheetPresentation/QMUISheetPresentationSupports.m",
    "chars": 17640,
    "preview": "//\n//  QMUISheetPresentationSupports.m\n//  QMUIKit\n//\n//  Created by molice on 2024/2/27.\n//  Copyright © 2024 QMUI Team"
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITableView.h",
    "chars": 997,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITableView.m",
    "chars": 2219,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITableViewCell.h",
    "chars": 3784,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITableViewCell.m",
    "chars": 16791,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITableViewHeaderFooterView.h",
    "chars": 2226,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITableViewHeaderFooterView.m",
    "chars": 7219,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITableViewProtocols.h",
    "chars": 1835,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITestView.h",
    "chars": 869,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITestView.m",
    "chars": 4440,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITextField.h",
    "chars": 3955,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITextField.m",
    "chars": 13110,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITextView.h",
    "chars": 4654,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITextView.m",
    "chars": 22097,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITheme/QMUITheme.h",
    "chars": 1054,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITheme/QMUIThemeManager.h",
    "chars": 6367,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITheme/QMUIThemeManager.m",
    "chars": 6334,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITheme/QMUIThemeManagerCenter.h",
    "chars": 1310,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITheme/QMUIThemeManagerCenter.m",
    "chars": 2242,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITheme/QMUIThemePrivate.h",
    "chars": 2789,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITheme/QMUIThemePrivate.m",
    "chars": 32285,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITheme/UIColor+QMUITheme.h",
    "chars": 2887,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITheme/UIColor+QMUITheme.m",
    "chars": 8825,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITheme/UIImage+QMUITheme.h",
    "chars": 3496,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITheme/UIImage+QMUITheme.m",
    "chars": 23598,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITheme/UIView+QMUITheme.h",
    "chars": 1957,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITheme/UIView+QMUITheme.m",
    "chars": 11431,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITheme/UIViewController+QMUITheme.h",
    "chars": 1320,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITheme/UIViewController+QMUITheme.m",
    "chars": 2813,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITheme/UIVisualEffect+QMUITheme.h",
    "chars": 3765,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITheme/UIVisualEffect+QMUITheme.m",
    "chars": 5003,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITips.h",
    "chars": 6589,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUITips.m",
    "chars": 13306,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIWeakObjectContainer.h",
    "chars": 1531,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIWeakObjectContainer.m",
    "chars": 2427,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIWindowSizeMonitor.h",
    "chars": 1941,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIWindowSizeMonitor.m",
    "chars": 9349,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIZoomImageView.h",
    "chars": 6244,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/QMUIZoomImageView.m",
    "chars": 40820,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/StaticTableView/QMUIStaticTableViewCellData.h",
    "chars": 6487,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/StaticTableView/QMUIStaticTableViewCellData.m",
    "chars": 5214,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  },
  {
    "path": "QMUIKit/QMUIComponents/StaticTableView/QMUIStaticTableViewCellDataSource.h",
    "chars": 3473,
    "preview": "/**\n * Tencent is pleased to support the open source community by making QMUI_iOS available.\n * Copyright (C) 2016-2021 "
  }
]

// ... and 186 more files (download for full content)

About this extraction

This page contains the full source code of the Tencent/QMUI_iOS GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 386 files (3.0 MB), approximately 795.1k tokens, and a symbol index with 128 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!