├── .codespellrc
├── .github
├── dependabot.yml
└── workflows
│ ├── CI.yml
│ ├── Package-AppFramework.yml
│ ├── Package-B9Foundation.yml
│ └── Package-InterfaceApp.yml
├── .gitignore
├── .gitlab-ci.yml
├── .swiftlint.yml
├── .vscode
└── settings.json
├── App.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── swiftpm
│ │ └── Package.resolved
└── xcshareddata
│ ├── xcdebugger
│ └── Breakpoints_v2.xcbkptlist
│ └── xcschemes
│ └── App.xcscheme
├── App.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── WorkspaceSettings.xcsettings
├── App
├── Assets
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon1024.png
│ │ │ ├── Icon120.png
│ │ │ ├── Icon152.png
│ │ │ ├── Icon167.png
│ │ │ ├── Icon180.png
│ │ │ ├── Icon40.png
│ │ │ ├── Icon58.png
│ │ │ ├── Icon60.png
│ │ │ ├── Icon76.png
│ │ │ ├── Icon80.png
│ │ │ └── Icon87.png
│ │ ├── Color
│ │ │ ├── Contents.json
│ │ │ ├── README.md
│ │ │ ├── background.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── on_background.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── on_primary.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── on_secondary.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── on_surface.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── placeholder.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── primary.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── primary_variant.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── secondary.colorset
│ │ │ │ └── Contents.json
│ │ │ └── surface.colorset
│ │ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── General UI
│ │ │ ├── Block
│ │ │ │ ├── Contents.json
│ │ │ │ ├── blank.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── blank.png
│ │ │ │ ├── solid.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── solid.png
│ │ │ │ └── underline_1px.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── block_underline.png
│ │ │ ├── Contents.json
│ │ │ ├── Control
│ │ │ │ ├── Contents.json
│ │ │ │ ├── button_rect.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── block_solid.pdf
│ │ │ │ ├── button_round44_fill.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── block_round44.pdf
│ │ │ │ ├── button_round44_frame.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── block_frame44.pdf
│ │ │ │ ├── checkbox_off.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── Off.pdf
│ │ │ │ └── checkbox_on.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── On.pdf
│ │ │ ├── Input
│ │ │ │ ├── Contents.json
│ │ │ │ ├── text_field_bg_disabled.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── text_field_bg_disabled.pdf
│ │ │ │ │ └── text_field_bg_disabled_drak.pdf
│ │ │ │ ├── text_field_bg_focused.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── text_field_bg_focused.pdf
│ │ │ │ │ └── text_field_bg_focused_drak.pdf
│ │ │ │ └── text_field_bg_normal.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── text_field_bg_normal.pdf
│ │ │ │ │ └── text_field_bg_normal_drak.pdf
│ │ │ ├── Navigation
│ │ │ │ ├── Contents.json
│ │ │ │ ├── nav_back.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── back.pdf
│ │ │ │ └── nav_back_layoutfix.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ └── back_layoutfiix.pdf
│ │ │ └── avatar_placeholder.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── avatar_placeholder.pdf
│ │ ├── Icon
│ │ │ ├── Contents.json
│ │ │ ├── test16.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── test_icon_16.png
│ │ │ │ ├── test_icon_16@2x.png
│ │ │ │ └── test_icon_16@3x.png
│ │ │ └── test24.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── test_icon_24.png
│ │ │ │ ├── test_icon_24@2x.png
│ │ │ │ └── test_icon_24@3x.png
│ │ └── List
│ │ │ ├── Contents.json
│ │ │ ├── list_accessory10.imageset
│ │ │ ├── Contents.json
│ │ │ └── list_accessory.pdf
│ │ │ ├── row_highlight.imageset
│ │ │ ├── Contents.json
│ │ │ └── row_highlight.png
│ │ │ └── table_refresh_indicator.imageset
│ │ │ ├── Contents.json
│ │ │ └── list_refresh_indicator.pdf
│ ├── Base.lproj
│ │ ├── InfoPlist.strings
│ │ └── Localizable.strings
│ └── README.md
├── Development Content
│ ├── GPX
│ │ ├── Run.gpx
│ │ └── SignificantChanges.gpx
│ ├── Playground.playground
│ │ ├── Contents.swift
│ │ └── contents.xcplayground
│ ├── README.md
│ └── UI Gallery.playground
│ │ ├── Pages
│ │ ├── RippleView.xcplaygroundpage
│ │ │ ├── Contents.swift
│ │ │ └── Resources
│ │ │ │ └── sample.png
│ │ ├── TOC.xcplaygroundpage
│ │ │ └── Contents.swift
│ │ └── TagView.xcplaygroundpage
│ │ │ └── Contents.swift
│ │ ├── contents.xcplayground
│ │ └── timeline.xctimeline
├── General
│ ├── Controls
│ │ ├── Button.swift
│ │ ├── GradientProgressViews.swift
│ │ ├── MBControlGroup.h
│ │ ├── MBControlGroup.m
│ │ ├── MBLayoutButton.swift
│ │ ├── MBOptionSwitch.swift
│ │ └── MBTouchEffectButton.swift
│ ├── Data Flow
│ │ └── DetailFetchControl.swift
│ ├── Effect
│ │ ├── CALayer+MBAnimationPersistence.h
│ │ ├── CALayer+MBAnimationPersistence.m
│ │ ├── CalloutView.swift
│ │ ├── Circle.swift
│ │ ├── CornerRadius.swift
│ │ ├── DissolveTransitioning.swift
│ │ ├── Gradient.swift
│ │ ├── MBAutoFadeoutView.h
│ │ ├── MBAutoFadeoutView.m
│ │ ├── MBIndefiniteRotationImageView.h
│ │ ├── MBIndefiniteRotationImageView.m
│ │ ├── MBMaskHiddenView.h
│ │ ├── MBMaskHiddenView.m
│ │ └── Shadow.swift
│ ├── Extensions
│ │ ├── Format
│ │ │ ├── Date+App.swift
│ │ │ ├── Date+Server.swift
│ │ │ ├── DateFormatter+App.swift
│ │ │ └── Number+App.swift
│ │ ├── Foundation
│ │ │ ├── Array+App.swift
│ │ │ ├── AttributedString+App.swift
│ │ │ ├── Codable+App.swift
│ │ │ ├── Collections+App.swift
│ │ │ ├── Geometry+App.swift
│ │ │ ├── NSArray+App.h
│ │ │ ├── NSArray+App.m
│ │ │ ├── NSPointArray+App.swift
│ │ │ ├── String+App.swift
│ │ │ ├── UIKit+App.h
│ │ │ └── URL+App.swift
│ │ ├── Language
│ │ │ ├── Do.swift
│ │ │ └── MBSwift.swift
│ │ ├── Resource
│ │ │ └── StoryboardCreation.swift
│ │ ├── UI App
│ │ │ ├── TextField+Action.swift
│ │ │ ├── UIDevice+App.swift
│ │ │ └── UILabel+App.swift
│ │ └── UI System
│ │ │ ├── CoreAnimation+App.swift
│ │ │ ├── UIApplication+App.swift
│ │ │ ├── UIButton+App.swift
│ │ │ ├── UICollectionView+App.swift
│ │ │ ├── UIKit+Compatibility.swift
│ │ │ ├── UITableView+App.swift
│ │ │ ├── UIViewController+App.h
│ │ │ ├── UIViewController+App.m
│ │ │ └── UIViewController+App.swift
│ ├── Form
│ │ ├── Input
│ │ │ ├── MBFormFieldVerifyControl+App.swift
│ │ │ └── MBFormFieldVerifyControl.swift
│ │ ├── MBCodeSendButton.swift
│ │ ├── MBFormSelectButton.swift
│ │ ├── MBFormSelectListViewController.h
│ │ ├── MBFormSelectListViewController.m
│ │ ├── MBKeyboardFloatContainer.h
│ │ ├── MBKeyboardFloatContainer.m
│ │ └── Picker
│ │ │ ├── MBDatePickerViewController.h
│ │ │ ├── MBDatePickerViewController.m
│ │ │ ├── MBValueMapPickerViewController.h
│ │ │ ├── MBValueMapPickerViewController.m
│ │ │ └── Picker.storyboard
│ ├── ImageView
│ │ ├── MBHighlightTintImageView.swift
│ │ ├── MBImageView.h
│ │ ├── MBImageView.m
│ │ ├── MBSkyImageView.h
│ │ ├── MBSkyImageView.m
│ │ ├── UIImageView+MBRenderingMode.h
│ │ └── UIImageView+MBRenderingMode.m
│ ├── Layout
│ │ ├── Auto Layout Fix
│ │ │ ├── MBFixWidthImageView.h
│ │ │ └── MBFixWidthImageView.m
│ │ ├── MBAdaptiveStackView.swift
│ │ ├── MBBottomLayoutView.h
│ │ ├── MBBottomLayoutView.m
│ │ ├── MBCellStackView.h
│ │ ├── MBCellStackView.m
│ │ ├── MBDesignView.h
│ │ ├── MBDesignView.m
│ │ ├── MBFlipCollapsibleView.h
│ │ ├── MBFlipCollapsibleView.m
│ │ ├── MBLayoutConstraint.h
│ │ ├── MBLayoutConstraint.m
│ │ ├── MBSceneStackView.h
│ │ ├── MBSceneStackView.m
│ │ ├── MBStateMachineView.h
│ │ ├── MBStateMachineView.m
│ │ └── ResizeObservableView.swift
│ ├── List
│ │ ├── CollectionView
│ │ │ ├── DataSource
│ │ │ │ ├── MBCollectionViewArrayDataSource.swift
│ │ │ │ ├── MBCollectionViewDataSource.h
│ │ │ │ └── MBCollectionViewDataSource.m
│ │ │ ├── EnumListView.swift
│ │ │ ├── Layout
│ │ │ │ ├── CollectionViewAutoSizingSectionView.swift
│ │ │ │ ├── MBCollectionViewCarouselLayout.h
│ │ │ │ ├── MBCollectionViewCarouselLayout.m
│ │ │ │ ├── MBCollectionViewColumnLayout.swift
│ │ │ │ ├── MBCollectionViewEqualColumnSpaceLayout.h
│ │ │ │ ├── MBCollectionViewEqualColumnSpaceLayout.m
│ │ │ │ ├── MBCollectionViewFlowLayout.h
│ │ │ │ └── MBCollectionViewFlowLayout.m
│ │ │ ├── MBCollectionListDisplayer.h
│ │ │ ├── MBCollectionListDisplayer.m
│ │ │ ├── MBCollectionView.h
│ │ │ ├── MBCollectionView.m
│ │ │ ├── MBEntitiesCollectionView.h
│ │ │ ├── MBEntitiesCollectionView.m
│ │ │ ├── MBSingleSectionCollectionView.h
│ │ │ ├── MBSingleSectionCollectionView.m
│ │ │ └── PullToFetch
│ │ │ │ ├── MBCollectionRefreshFooterView.h
│ │ │ │ ├── MBCollectionRefreshFooterView.m
│ │ │ │ └── MBCollectionRefreshFooterView.xib
│ │ ├── ListStateView.swift
│ │ ├── MBListDataSource.h
│ │ ├── MBListDataSource.m
│ │ ├── MBListDateItem.h
│ │ ├── MBListDateItem.m
│ │ ├── TableView
│ │ │ ├── DataSource
│ │ │ │ ├── MBTableViewArrayDataSource.swift
│ │ │ │ ├── MBTableViewDataSource.h
│ │ │ │ ├── MBTableViewDataSource.m
│ │ │ │ └── MBTableViewDataSource.swift
│ │ │ ├── MBEntitiesTableView.h
│ │ │ ├── MBEntitiesTableView.m
│ │ │ ├── MBTableHeaderFooterView.h
│ │ │ ├── MBTableHeaderFooterView.m
│ │ │ ├── MBTableListDisplayer.h
│ │ │ ├── MBTableListDisplayer.m
│ │ │ ├── MBTableView.h
│ │ │ ├── MBTableView.m
│ │ │ ├── MBTableViewController.h
│ │ │ ├── MBTableViewController.m
│ │ │ └── PullToFetch
│ │ │ │ ├── MBRefreshFooterView.h
│ │ │ │ ├── MBRefreshFooterView.m
│ │ │ │ ├── MBRefreshFooterView.xib
│ │ │ │ ├── MBRefreshHeaderView.h
│ │ │ │ ├── MBRefreshHeaderView.m
│ │ │ │ ├── MBRefreshHeaderView.xib
│ │ │ │ ├── MBTableViewPullToFetchControl.h
│ │ │ │ └── MBTableViewPullToFetchControl.m
│ │ └── TagView.swift
│ ├── Navigation
│ │ ├── MBNavigationBar.h
│ │ ├── MBNavigationBar.m
│ │ ├── MBNavigationItem.h
│ │ ├── MBNavigationItem.m
│ │ ├── MBNavigationTitleView.h
│ │ ├── MBNavigationTitleView.m
│ │ ├── MBRoundBarButtonItem.h
│ │ └── MBRoundBarButtonItem.m
│ ├── Others
│ │ ├── UIAnyGestureRecognizer.h
│ │ └── UIAnyGestureRecognizer.m
│ ├── README.md
│ ├── ScrollView
│ │ ├── MBKeyboardAdjustScrollView.h
│ │ ├── MBKeyboardAdjustScrollView.m
│ │ ├── MBParallaxView.h
│ │ ├── MBParallaxView.m
│ │ ├── MBScrollViewOffsetObserver.h
│ │ └── MBScrollViewOffsetObserver.m
│ ├── Search
│ │ ├── MBSearchTextField.h
│ │ ├── MBSearchTextField.m
│ │ ├── MBSearchViewController.h
│ │ ├── MBSearchViewController.m
│ │ ├── SearchHighlight.swift
│ │ └── SearchTextField.swift
│ ├── Segue
│ │ └── MBModalPresentSegue.swift
│ ├── Tab
│ │ ├── MBPageScrollView.h
│ │ ├── MBPageScrollView.m
│ │ ├── MBTabControl.h
│ │ ├── MBTabControl.m
│ │ ├── MBTabController.h
│ │ ├── MBTabController.m
│ │ ├── MBTabScrollView.h
│ │ └── MBTabScrollView.m
│ ├── Text
│ │ ├── MBBadgeLabel.h
│ │ ├── MBBadgeLabel.m
│ │ ├── MBTextCountLabel.h
│ │ ├── MBTextCountLabel.m
│ │ ├── MBTextView.h
│ │ ├── MBTextView.m
│ │ ├── MBVauleLabel.h
│ │ ├── MBVauleLabel.m
│ │ └── TextField.swift
│ └── UIKit+IBInspectable
│ │ ├── UIButton+FontShrink.swift
│ │ ├── UICollectionView+IBInspectable.h
│ │ ├── UIKit+DynamicType.h
│ │ ├── UIKit+DynamicType.m
│ │ ├── UIKit+IBInspectable.h
│ │ ├── UILabel+ParagraphStyle.swift
│ │ ├── UIScrollView+IBInspectable.h
│ │ ├── UITextView+ContainerInset.h
│ │ ├── UITextView+ContainerInset.m
│ │ ├── UIViewController+Appearance.swift
│ │ └── UIViewController+InterfaceOrientation.swift
├── Info.plist
├── Launching
│ ├── ApplicationCondition.swift
│ ├── ApplicationDelegate.swift
│ ├── LaunchScreen.storyboard
│ ├── NavigationController+Router.swift
│ ├── NavigationController+Tab.swift
│ ├── NavigationController.swift
│ └── RootViewController.swift
├── Model
│ ├── Account
│ │ ├── Account.swift
│ │ └── AccountEntities.swift
│ ├── JSON
│ │ ├── CommentEntity.swift
│ │ ├── README.md
│ │ ├── TopicEntity.swift
│ │ └── UserEntity.swift
│ ├── README.md
│ ├── Support
│ │ ├── FileURL.swift
│ │ ├── IdentifierEquatable.swift
│ │ ├── JSONValueTransformer+App.m
│ │ ├── MBErrorCode.h
│ │ └── MBErrorCode.m
│ ├── Types.swift
│ └── UserDefaults.swift
├── Scene
│ ├── Home
│ │ └── HomeVC.swift
│ ├── Login
│ │ ├── Login.storyboard
│ │ ├── LoginForms.swift
│ │ └── LoginVCs.swift
│ ├── Main.storyboard
│ ├── More
│ │ └── MoreVC.swift
│ ├── Topic
│ │ ├── Topic.storyboard
│ │ ├── TopicDetailVC.swift
│ │ ├── TopicListDisplayer.swift
│ │ └── TopicListVCs.swift
│ └── User
│ │ └── UserAvatarView.swift
├── Service
│ ├── AppNewVersionChecker
│ │ └── AppNewVersionChecker.swift
│ ├── Current.swift
│ ├── Database
│ │ ├── DBManager+KVStorage.swift
│ │ └── DBManager.swift
│ ├── Logging
│ │ └── Logger.swift
│ ├── MBAudioRecorder
│ │ ├── MBAudioRecorder.h
│ │ └── MBAudioRecorder.m
│ ├── MBDebug
│ │ ├── Components
│ │ │ ├── MBFlexInterface.h
│ │ │ ├── MBFlexInterface.m
│ │ │ ├── MBNavigationController+MBDebugReleaseChecking.h
│ │ │ └── MBNavigationController+MBDebugReleaseChecking.m
│ │ └── MBDebugLiveCountChecker.swift
│ ├── MBFileUploader
│ │ ├── API+FileUpload.h
│ │ ├── OSS
│ │ │ ├── API+FileUpload.m
│ │ │ ├── OSSConfigEntity.h
│ │ │ └── OSSConfigEntity.m
│ │ └── RFAPI
│ │ │ └── API+FileUpload.m
│ ├── MBImagePicker
│ │ ├── MBPublishImagePicker.h
│ │ └── MBPublishImagePicker.m
│ ├── MBImageRenderer
│ │ ├── MBImageRenderer.h
│ │ └── MBImageRenderer.m
│ ├── MBNotificationCenter
│ │ ├── BadgeManager.swift
│ │ ├── MBNotificationBadgeManager.h
│ │ ├── MBNotificationBadgeManager.m
│ │ ├── MBNotificationIndicatorViews.h
│ │ └── MBNotificationIndicatorViews.m
│ ├── MBPublishSession
│ │ ├── MBPublishSession.h
│ │ └── MBPublishSession.m
│ ├── MBShareManager
│ │ ├── Origin
│ │ │ ├── MBShareManager.h
│ │ │ └── MBShareManager.m
│ │ └── SharePanelVC.swift
│ ├── MessageManager
│ │ ├── AlertController.swift
│ │ └── MessageManager.swift
│ ├── Networking
│ │ ├── API.swift
│ │ ├── APIDefine.plist
│ │ └── APIResponseSerializer.swift
│ ├── PushManager
│ │ ├── getui
│ │ │ ├── PushManager.h
│ │ │ └── PushManager.m
│ │ ├── jpush
│ │ │ └── PushManager.swift
│ │ └── native
│ │ │ └── PushManager.swift
│ └── README.md
└── Supporting Files
│ ├── Common.h
│ ├── OCBridging-Header.h
│ ├── PrivacyInfo.xcprivacy
│ ├── alpha.entitlements
│ ├── debug.entitlements
│ └── release.entitlements
├── Frameworks
├── B9Lib
│ └── B9Crypto.swift
├── MBAppKit
│ ├── Components
│ │ ├── MBButton.swift
│ │ ├── MBNavigationController.swift
│ │ ├── MBTextField.swift
│ │ └── MBWorker.swift
│ └── MBAppKit
│ │ ├── Category
│ │ ├── NSObject+MBAppKit.h
│ │ └── NSObject+MBAppKit.m
│ │ ├── MBAPI.swift
│ │ ├── MBApplicationDelegate
│ │ ├── MBApplicationDelegate.h
│ │ └── MBApplicationDelegate.m
│ │ ├── MBGeneral.swift
│ │ ├── MBGeneral
│ │ ├── MBGeneralCallback.h
│ │ ├── MBGeneralCallback.m
│ │ ├── MBGeneralCellResponding.h
│ │ ├── MBGeneralCellResponding.m
│ │ ├── MBGeneralItemExchanging.h
│ │ ├── MBGeneralItemExchanging.m
│ │ ├── MBGeneralListDisplaying.h
│ │ ├── MBGeneralListExchanging.h
│ │ ├── MBGeneralListExchanging.m
│ │ ├── MBGeneralSegue.h
│ │ ├── MBGeneralSegue.m
│ │ ├── MBGeneralSelection.h
│ │ ├── MBGeneralViewControllerStateTransitions.h
│ │ ├── MBGeneralViewControllerStateTransitions.m
│ │ └── README.md
│ │ └── MBModel
│ │ ├── MBModel.h
│ │ └── MBModel.m
└── README.md
├── Packages
├── Action
│ ├── LICENSE
│ ├── Package.swift
│ ├── README.md
│ ├── Sources
│ │ └── B9Action
│ │ │ └── B9Action.swift
│ └── Tests
│ │ └── B9ActionTests
│ │ └── B9ActionTests.swift
├── AppFramework
│ ├── .gitignore
│ ├── .swiftpm
│ │ ├── AppFramework.xctestplan
│ │ └── xcode
│ │ │ ├── package.xcworkspace
│ │ │ └── xcshareddata
│ │ │ │ └── WorkspaceSettings.xcsettings
│ │ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── AppFramework.xcscheme
│ ├── AppFramework.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── swiftpm
│ │ │ └── Package.resolved
│ ├── Package.resolved
│ ├── Package.swift
│ ├── README.md
│ ├── Sources
│ │ └── AppFramework
│ │ │ ├── AppError.swift
│ │ │ ├── Extensions
│ │ │ ├── Collection+AF.swift
│ │ │ └── UIImage+ImageSet.swift
│ │ │ ├── HasItem.swift
│ │ │ ├── MBAssert.swift
│ │ │ ├── Managers
│ │ │ ├── AccountManager.swift
│ │ │ └── VersionManager.swift
│ │ │ ├── ObjectPool.swift
│ │ │ ├── Support Types
│ │ │ ├── CollectionStateTracker.swift
│ │ │ └── Observation.swift
│ │ │ └── UI
│ │ │ └── MBGroupSelectionControl.swift
│ ├── Tests
│ │ └── AppFrameworkTests
│ │ │ ├── AppErrorTests.swift
│ │ │ ├── Assets
│ │ │ └── Test.xcassets
│ │ │ │ ├── Contents.json
│ │ │ │ └── zs_test_1.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── image_solid_123456.png
│ │ │ ├── Extensions
│ │ │ ├── CollectionExTests.swift
│ │ │ └── UIImageImageSetTests.swift
│ │ │ ├── HasItemTests.swift
│ │ │ ├── Helper
│ │ │ ├── UIKit+Test.swift
│ │ │ └── XCTest+.swift
│ │ │ ├── Managers
│ │ │ ├── AccountManagerTests.swift
│ │ │ └── VersionManagerTests.swift
│ │ │ ├── Mocked
│ │ │ └── SimpleKeyValueStorage.swift
│ │ │ ├── ObjectPoolTests.swift
│ │ │ ├── Support Types
│ │ │ ├── CollectionStateTrackerTests.swift
│ │ │ └── ObservationTests.swift
│ │ │ └── UI
│ │ │ └── MBGroupSelectionControlTests.swift
│ └── run_unit_tests.command
├── AssociatedObject
│ ├── LICENSE
│ ├── Package.swift
│ ├── README.md
│ ├── Sources
│ │ └── B9AssociatedObject
│ │ │ └── B9AssociatedObject.swift
│ └── Tests
│ │ └── MainTests
│ │ └── MainTests.swift
├── B9Foundation
│ ├── .gitignore
│ ├── .swiftpm
│ │ └── xcode
│ │ │ ├── package.xcworkspace
│ │ │ └── xcshareddata
│ │ │ │ └── WorkspaceSettings.xcsettings
│ │ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── B9Foundation.xcscheme
│ ├── B9Foundation.xcworkspace
│ │ └── contents.xcworkspacedata
│ ├── Package.swift
│ ├── README.md
│ ├── Sources
│ │ └── B9Foundation
│ │ │ ├── Date+.swift
│ │ │ ├── DateMock.swift
│ │ │ ├── Result+.swift
│ │ │ ├── UIImage+.swift
│ │ │ └── UIResponder+.swift
│ ├── Tests
│ │ └── B9FoundationTests
│ │ │ ├── DateExTests.swift
│ │ │ ├── DateMockTests.swift
│ │ │ ├── Helper.swift
│ │ │ ├── Resources
│ │ │ ├── image_diff_size.png
│ │ │ ├── image_solid_123456.png
│ │ │ └── image_solid_123456_diff.png
│ │ │ ├── ResultExTests.swift
│ │ │ ├── UIImageExTests.swift
│ │ │ └── UIResponderExTests.swift
│ └── run_unit_tests.command
├── BuildSystem
│ ├── .swiftpm
│ │ └── xcode
│ │ │ └── package.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ ├── Package.swift
│ ├── Sources
│ │ └── BuildSystem
│ │ │ ├── Configuration.swift
│ │ │ ├── Support
│ │ │ ├── Command.swift
│ │ │ ├── GError.swift
│ │ │ ├── Log.swift
│ │ │ └── XCContext.swift
│ │ │ ├── Task
│ │ │ ├── BuildCount.swift
│ │ │ └── HighlightComments.swift
│ │ │ └── main.swift
│ └── Tests
│ │ └── ScriptTests
│ │ ├── CommandLineTests.swift
│ │ └── HighlightCommentsTests.swift
├── Condition
│ ├── LICENSE
│ ├── Package.swift
│ ├── README.md
│ ├── Sources
│ │ └── B9Condition
│ │ │ └── B9Condition.swift
│ └── Tests
│ │ └── B9ConditionTests
│ │ └── B9ConditionTests.swift
├── Debug
│ ├── LICENSE
│ ├── Package.swift
│ ├── README.md
│ └── Sources
│ │ └── B9Debug
│ │ ├── B9Debug.m
│ │ └── include
│ │ └── B9Debug.h
├── Debugger
│ ├── Package.swift
│ ├── README.md
│ ├── Sources
│ │ └── Debugger
│ │ │ ├── ActionItem.swift
│ │ │ ├── Debugger+Internal.swift
│ │ │ ├── Debugger.swift
│ │ │ ├── HasItemSupport.swift
│ │ │ ├── ListDiscovering.swift
│ │ │ └── UI
│ │ │ ├── Debugger.storyboard
│ │ │ ├── DescriptionViewController.swift
│ │ │ ├── FloatViewController.swift
│ │ │ ├── TriggerButton.swift
│ │ │ └── Window.swift
│ └── Tests
│ │ └── DebuggerTests
│ │ └── TriggerButtonTests.swift
├── InterfaceApp
│ ├── .gitignore
│ ├── .swiftpm
│ │ └── xcode
│ │ │ ├── package.xcworkspace
│ │ │ └── xcshareddata
│ │ │ │ └── WorkspaceSettings.xcsettings
│ │ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── InterfaceApp.xcscheme
│ ├── InterfaceApp.xcworkspace
│ │ └── contents.xcworkspacedata
│ ├── Package.swift
│ ├── README.md
│ ├── Sources
│ │ └── InterfaceApp
│ │ │ ├── IAAccount.swift
│ │ │ └── IASimpleKeyValueStorage.swift
│ ├── Tests
│ │ └── InterfaceAppTests
│ │ │ ├── IAAccountTests.swift
│ │ │ └── IASimpleKeyValueStorageTests.swift
│ └── run_unit_tests.command
├── MulticastDelegate
│ ├── LICENSE
│ ├── Package.swift
│ ├── README.md
│ ├── Sources
│ │ └── B9MulticastDelegate
│ │ │ └── B9MulticastDelegate.swift
│ └── Tests
│ │ └── B9MulticastDelegateTests
│ │ └── B9MulticastDelegateTests.swift
├── Pulse
│ ├── LICENSE
│ ├── Package.swift
│ ├── README.md
│ └── Sources
│ │ ├── Pulse
│ │ ├── Helpers
│ │ │ ├── CoreData+Extensions.swift
│ │ │ ├── Extensions.swift
│ │ │ ├── LoggerSession.swift
│ │ │ ├── PulseDocument.swift
│ │ │ └── Version.swift
│ │ ├── LoggerStore
│ │ │ ├── LoggerStore+Configuration.swift
│ │ │ ├── LoggerStore+Entities.swift
│ │ │ ├── LoggerStore+Event.swift
│ │ │ ├── LoggerStore+Extensions.swift
│ │ │ ├── LoggerStore+Info.swift
│ │ │ ├── LoggerStore+Level.swift
│ │ │ ├── LoggerStore+Model.swift
│ │ │ └── LoggerStore.swift
│ │ ├── NetworkLogger
│ │ │ ├── NetworkLogger+Entities.swift
│ │ │ ├── NetworkLogger.swift
│ │ │ ├── NetworkLoggerInsights.swift
│ │ │ ├── URLSessionProxy.swift
│ │ │ └── URLSessionProxyDelegate.swift
│ │ └── RemoteLogger
│ │ │ ├── RemoteLogger-Connection.swift
│ │ │ ├── RemoteLogger-Helpers.swift
│ │ │ ├── RemoteLogger-Protocol.swift
│ │ │ ├── RemoteLogger-Store.swift
│ │ │ └── RemoteLogger.swift
│ │ ├── PulseLogHandler
│ │ └── PersistentLogHandler.swift
│ │ └── PulseUI
│ │ ├── Extensions
│ │ ├── Formatters.swift
│ │ ├── Foundation+Extensions.swift
│ │ ├── NSTableView+Extensions.swift
│ │ ├── PulseCore+Extensions.swift
│ │ ├── SwiftUIExtensions.swift
│ │ └── UITableView+Extensions.swift
│ │ ├── Features
│ │ ├── Archive
│ │ │ └── DocumentBrowser.swift
│ │ ├── Charts
│ │ │ └── LoggerStoreSizeChart.swift
│ │ ├── Console
│ │ │ ├── ConsoleMessageTableCell.swift
│ │ │ ├── ConsoleMessageView.swift
│ │ │ ├── ConsoleMessageViewModel.swift
│ │ │ ├── ConsoleMessagesForEach.swift
│ │ │ ├── ConsoleNetworkRequestTableCell.swift
│ │ │ ├── ConsoleNetworkRequestView.swift
│ │ │ ├── ConsoleNetworkRequestViewModel.swift
│ │ │ ├── ConsoleTableView-ios.swift
│ │ │ ├── ConsoleTableView-macos.swift
│ │ │ ├── ConsoleToolbar-macos.swift
│ │ │ ├── ConsoleView-ios.swift
│ │ │ ├── ConsoleView-tvos.swift
│ │ │ ├── ConsoleView-watchos.swift
│ │ │ └── ConsoleViewModel.swift
│ │ ├── Filters
│ │ │ ├── ConsoleFiltersLabelsPickerView.swift
│ │ │ ├── ConsoleFiltersView.swift
│ │ │ ├── ConsoleSearchCriteria.swift
│ │ │ ├── ConsoleSearchCriteriaViewModel.swift
│ │ │ ├── CustomFilterView.swift
│ │ │ ├── CustomNetworkFilterView.swift
│ │ │ ├── DateRangePicker.swift
│ │ │ ├── FilterSectionHeader.swift
│ │ │ ├── FiltersHelpers.swift
│ │ │ ├── NetworkFiltersVIew.swift
│ │ │ ├── NetworkSearchCriteria.swift
│ │ │ └── NetworkSearchCriteriaViewModel.swift
│ │ ├── Insights
│ │ │ ├── InsightsView.swift
│ │ │ └── NetworkInsightsRequestsList.swift
│ │ ├── Inspector
│ │ │ ├── Metrics
│ │ │ │ ├── NetworkInspectorMetricsDetailsView.swift
│ │ │ │ ├── NetworkInspectorMetricsView.swift
│ │ │ │ ├── NetworkInspectorTransactionView.swift
│ │ │ │ ├── NetworkInspectorTransferInfoView.swift
│ │ │ │ └── TimingViewModel+Metrics.swift
│ │ │ ├── NetworkInspectorHeadersTabView.swift
│ │ │ ├── NetworkInspectorHeadersView.swift
│ │ │ ├── NetworkInspectorMetricsTabView.swift
│ │ │ ├── NetworkInspectorRequestVIew.swift
│ │ │ ├── NetworkInspectorSummaryView.swift
│ │ │ ├── NetworkInspectorSummaryViewModel.swift
│ │ │ ├── NetworkInspectorTransactionsListView.swift
│ │ │ ├── NetworkInspectorView.swift
│ │ │ ├── NetworkInspectorViewModel.swift
│ │ │ └── NeworkInspectorResponseView.swift
│ │ ├── Main
│ │ │ ├── MainView-ios-tvos.swift
│ │ │ ├── MainView-macos.swift
│ │ │ ├── MainView-watchos.swift
│ │ │ ├── MainViewModel-ios.swift
│ │ │ └── MainViewModel-macos.swift
│ │ ├── MessageDetails
│ │ │ ├── ConsoleMessageDetailsRouter.swift
│ │ │ ├── ConsoleMessageDetailsView.swift
│ │ │ ├── ConsoleMessageDetailsViewModel.swift
│ │ │ └── ConsoleMessageMetadataView.swift
│ │ ├── Network
│ │ │ ├── NetworkView.swift
│ │ │ └── NetworkViewModel.swift
│ │ ├── Pins
│ │ │ ├── PinsView-ios.swift
│ │ │ └── PinsViewModel.swift
│ │ └── Settings
│ │ │ ├── RemoteLoggerSettingsView.swift
│ │ │ ├── SettingsView-macos.swift
│ │ │ ├── SettingsView.swift
│ │ │ ├── SettingsViewModel.swift
│ │ │ └── StoreDetailsView.swift
│ │ ├── Helpers
│ │ ├── DecodingErrorsPreviews.swift
│ │ ├── FetchedObjects.swift
│ │ ├── HTMLPrettyPrint.swift
│ │ ├── JSONPrinter.swift
│ │ ├── LoggerSync.swift
│ │ ├── ManagedObjectTextSearch.swift
│ │ ├── ManagedObjectsObserver.swift
│ │ ├── Regex.swift
│ │ ├── Render.swift
│ │ ├── ShareItems.swift
│ │ ├── SpinnerView.swift
│ │ └── UIKit+Extensions.swift
│ │ ├── Mocks
│ │ ├── MockStore.swift
│ │ ├── MockStoreConfiguration.swift
│ │ └── MockTask.swift
│ │ ├── UIKItSupport
│ │ └── MainViewController.swift
│ │ ├── UXKit
│ │ └── UXKit.swift
│ │ └── Views
│ │ ├── BadgeView.swift
│ │ ├── Border.swift
│ │ ├── Checkbox.swift
│ │ ├── ContextMenus.swift
│ │ ├── FileViewer.swift
│ │ ├── FileViewerViewModel.swift
│ │ ├── ImageViewer.swift
│ │ ├── InfoRow.swift
│ │ ├── KeyValueGridView.swift
│ │ ├── KeyValueSectionView+Extensions.swift
│ │ ├── KeyValueSectionView.swift
│ │ ├── LargeSectionHeader.swift
│ │ ├── NotSplitView.swift
│ │ ├── PDFRepresentedView.swift
│ │ ├── PinButton.swift
│ │ ├── PlaceholderView.swift
│ │ ├── RichTextView-watchos.swift
│ │ ├── RichTextView.swift
│ │ ├── SearchBar-ios.swift
│ │ ├── SearchBar-macos.swift
│ │ ├── ShareView.swift
│ │ ├── Spinner.swift
│ │ ├── StoreShareView.swift
│ │ ├── TimingView.swift
│ │ ├── ToastView.swift
│ │ └── WebView.swift
└── swift-log
│ ├── .gitignore
│ ├── LICENSE.txt
│ ├── NOTICE.txt
│ ├── Package.swift
│ ├── README.md
│ └── Sources
│ └── Logging
│ ├── Locks.swift
│ ├── LogHandler.swift
│ └── Logging.swift
├── Podfile
├── Podfile.lock
├── README.md
├── bootstrap.command
├── ci_scripts
├── BuildCountRecord.plist
├── README.md
├── ci_post_clone.sh
├── gitlab.sh
├── sort-Xcode-project-file.pl
├── sort_projects.sh
├── 🎯 Pod Install.command
├── 🎯 Pod Update.command
├── 🎯 安装项目依赖.command
├── 🎯 打包上传 fir.im.command
└── 🎯 整理项目文件.command
├── fastlane
├── Appfile
├── Fastfile
└── README.md
├── 安装 git hook 脚本.command
└── 获取项目演示.command
/.codespellrc:
--------------------------------------------------------------------------------
1 | # https://github.com/codespell-project/codespell
2 | # 拼写检查命令行工具,可用 Homebrew 安装:brew install codespell
3 |
4 | [codespell]
5 | count =
6 | skip = ./.git,./Carthage/,./Pods,.build,Build,./Packages/Pulse
7 | check-filenames =
8 | ignore-words-list = anumber,dout,inout
9 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # https://help.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates
2 |
3 | version: 2
4 | updates:
5 | - package-ecosystem: "github-actions"
6 | directory: "/"
7 | schedule:
8 | interval: "weekly"
9 |
--------------------------------------------------------------------------------
/.github/workflows/Package-AppFramework.yml:
--------------------------------------------------------------------------------
1 | name: "Package: AppFramework"
2 |
3 | on:
4 | pull_request:
5 | paths:
6 | - '.github/workflows/Package-AppFramework.yml'
7 | - 'Packages/AppFramework/**'
8 | workflow_dispatch:
9 |
10 | concurrency:
11 | group: ${{ github.workflow }}-${{ github.ref }}
12 | cancel-in-progress: true
13 |
14 | jobs:
15 | test:
16 | runs-on: macos-latest
17 | timeout-minutes: 5
18 |
19 | steps:
20 | - uses: actions/checkout@v4
21 |
22 | - name: Run Package Tests
23 | run: |
24 | ./Packages/AppFramework/run_unit_tests.command
25 |
--------------------------------------------------------------------------------
/.github/workflows/Package-B9Foundation.yml:
--------------------------------------------------------------------------------
1 | name: "Package: B9Foundation"
2 |
3 | on:
4 | pull_request:
5 | paths:
6 | - '.github/workflows/Package-B9Foundation.yml'
7 | - 'Packages/B9Foundation/**'
8 | workflow_dispatch:
9 |
10 | concurrency:
11 | group: ${{ github.workflow }}-${{ github.ref }}
12 | cancel-in-progress: true
13 |
14 | jobs:
15 | test:
16 | runs-on: macos-latest
17 | timeout-minutes: 5
18 |
19 | steps:
20 | - uses: actions/checkout@v4
21 |
22 | - name: Run Package Tests
23 | run: |
24 | ./Packages/B9Foundation/run_unit_tests.command
25 |
--------------------------------------------------------------------------------
/.github/workflows/Package-InterfaceApp.yml:
--------------------------------------------------------------------------------
1 | name: "Package: InterfaceApp"
2 |
3 | on:
4 | pull_request:
5 | paths:
6 | - '.github/workflows/Package-InterfaceApp.yml'
7 | - 'Packages/InterfaceApp/**'
8 | workflow_dispatch:
9 |
10 | concurrency:
11 | group: ${{ github.workflow }}-${{ github.ref }}
12 | cancel-in-progress: true
13 |
14 | jobs:
15 | test:
16 | runs-on: macos-latest
17 | timeout-minutes: 5
18 |
19 | steps:
20 | - uses: actions/checkout@v4
21 |
22 | - name: Run Package Tests
23 | run: |
24 | ./Packages/InterfaceApp/run_unit_tests.command
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Xcode Project
3 | **/*.xcodeproj/xcuserdata/
4 | **/*.xcworkspace/xcuserdata/
5 | **/.swiftpm/xcode/xcuserdata/
6 | **/*.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
7 | **/*.xcworkspace/xcshareddata/*.xccheckout
8 | **/*.xcworkspace/xcshareddata/*.xcscmblueprint
9 | **/*.playground/**/timeline.xctimeline
10 | .idea/
11 |
12 | # Build
13 | **/.build/
14 | **/Build/
15 | DerivedData/
16 | *.ipa
17 |
18 | # Carthage
19 | Carthage/
20 |
21 | # CocoaPods
22 | Pods/
23 |
24 | # fastlane
25 | fastlane/report.xml
26 | fastlane/Preview.html
27 | fastlane/screenshots
28 | fastlane/test_output
29 | fastlane/sign&cert
30 |
31 | # CSV
32 | *.orig
33 | .svn
34 |
35 | # Other
36 | *~
37 | .DS_Store
38 | *.swp
39 | *.save
40 | ._*
41 | *.bak
42 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "**/xcuserdata/": true,
4 | "**/build/": true,
5 | "**/Pods/": true,
6 | "**/*.save": true
7 | }
8 | }
--------------------------------------------------------------------------------
/App.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "B9MulticastDelegate",
6 | "repositoryURL": "https://github.com/b9swift/MulticastDelegate",
7 | "state": {
8 | "branch": null,
9 | "revision": "db7366999a931637c9240cf2103f0f946d1ba664",
10 | "version": "1.0.0"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/App.xcodeproj/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/App.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
12 |
13 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/App.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon1024.png
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon120.png
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon152.png
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon167.png
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon180.png
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon40.png
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon58.png
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon60.png
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon76.png
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon80.png
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/AppIcon.appiconset/Icon87.png
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Color/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Color/README.md:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Color/background.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFF",
9 | "green" : "0xFF",
10 | "red" : "0xFF"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x12",
27 | "green" : "0x12",
28 | "red" : "0x12"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Color/on_background.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x00",
9 | "green" : "0x00",
10 | "red" : "0x00"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0xFF",
27 | "green" : "0xFF",
28 | "red" : "0xFF"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Color/on_primary.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFF",
9 | "green" : "0xFF",
10 | "red" : "0xFF"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0xFF",
27 | "green" : "0xFF",
28 | "red" : "0xFF"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Color/on_secondary.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x00",
9 | "green" : "0x00",
10 | "red" : "0x00"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x00",
27 | "green" : "0x00",
28 | "red" : "0x00"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Color/on_surface.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x00",
9 | "green" : "0x00",
10 | "red" : "0x00"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0xFF",
27 | "green" : "0xFF",
28 | "red" : "0xFF"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Color/placeholder.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | },
6 | "colors" : [
7 | {
8 | "idiom" : "universal",
9 | "color" : {
10 | "color-space" : "srgb",
11 | "components" : {
12 | "red" : "0.235",
13 | "alpha" : "0.300",
14 | "blue" : "0.263",
15 | "green" : "0.235"
16 | }
17 | }
18 | },
19 | {
20 | "idiom" : "universal",
21 | "appearances" : [
22 | {
23 | "appearance" : "luminosity",
24 | "value" : "dark"
25 | }
26 | ],
27 | "color" : {
28 | "color-space" : "srgb",
29 | "components" : {
30 | "red" : "0.922",
31 | "alpha" : "0.300",
32 | "blue" : "0.961",
33 | "green" : "0.922"
34 | }
35 | }
36 | }
37 | ]
38 | }
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Color/primary.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFB",
9 | "green" : "0x8F",
10 | "red" : "0x2E"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0xD6",
27 | "green" : "0x6B",
28 | "red" : "0x0A"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Color/primary_variant.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "214",
9 | "green" : "86",
10 | "red" : "88"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "230",
27 | "green" : "92",
28 | "red" : "94"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Color/secondary.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "250",
9 | "green" : "200",
10 | "red" : "90"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "255",
27 | "green" : "210",
28 | "red" : "100"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Color/surface.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.933",
9 | "green" : "0.933",
10 | "red" : "0.933"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x12",
27 | "green" : "0x12",
28 | "red" : "0x12"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Block/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Block/blank.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "blank.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Block/blank.imageset/blank.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/General UI/Block/blank.imageset/blank.png
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Block/solid.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "solid.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Block/solid.imageset/solid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/General UI/Block/solid.imageset/solid.png
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Block/underline_1px.imageset/block_underline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/General UI/Block/underline_1px.imageset/block_underline.png
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Control/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Control/button_rect.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "block_solid.pdf",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "template"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Control/button_rect.imageset/block_solid.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/General UI/Control/button_rect.imageset/block_solid.pdf
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Control/button_round44_fill.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "block_round44.pdf",
5 | "idiom" : "universal",
6 | "resizing" : {
7 | "cap-insets" : {
8 | "left" : 23,
9 | "right" : 24
10 | },
11 | "center" : {
12 | "mode" : "tile",
13 | "width" : 1
14 | },
15 | "mode" : "3-part-horizontal"
16 | },
17 | "scale" : "1x"
18 | },
19 | {
20 | "idiom" : "universal",
21 | "scale" : "2x"
22 | },
23 | {
24 | "idiom" : "universal",
25 | "scale" : "3x"
26 | }
27 | ],
28 | "info" : {
29 | "author" : "xcode",
30 | "version" : 1
31 | },
32 | "properties" : {
33 | "template-rendering-intent" : "template"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Control/button_round44_fill.imageset/block_round44.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/General UI/Control/button_round44_fill.imageset/block_round44.pdf
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Control/button_round44_frame.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "block_frame44.pdf",
5 | "idiom" : "universal",
6 | "resizing" : {
7 | "cap-insets" : {
8 | "left" : 23,
9 | "right" : 24
10 | },
11 | "center" : {
12 | "mode" : "tile",
13 | "width" : 1
14 | },
15 | "mode" : "3-part-horizontal"
16 | },
17 | "scale" : "1x"
18 | },
19 | {
20 | "idiom" : "universal",
21 | "scale" : "2x"
22 | },
23 | {
24 | "idiom" : "universal",
25 | "scale" : "3x"
26 | }
27 | ],
28 | "info" : {
29 | "author" : "xcode",
30 | "version" : 1
31 | },
32 | "properties" : {
33 | "template-rendering-intent" : "template"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Control/button_round44_frame.imageset/block_frame44.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/General UI/Control/button_round44_frame.imageset/block_frame44.pdf
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Control/checkbox_off.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Off.pdf",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "template"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Control/checkbox_off.imageset/Off.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/General UI/Control/checkbox_off.imageset/Off.pdf
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Control/checkbox_on.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "On.pdf",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "template"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Control/checkbox_on.imageset/On.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/General UI/Control/checkbox_on.imageset/On.pdf
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Input/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Input/text_field_bg_disabled.imageset/text_field_bg_disabled.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/General UI/Input/text_field_bg_disabled.imageset/text_field_bg_disabled.pdf
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Input/text_field_bg_disabled.imageset/text_field_bg_disabled_drak.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/General UI/Input/text_field_bg_disabled.imageset/text_field_bg_disabled_drak.pdf
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Input/text_field_bg_focused.imageset/text_field_bg_focused.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/General UI/Input/text_field_bg_focused.imageset/text_field_bg_focused.pdf
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Input/text_field_bg_focused.imageset/text_field_bg_focused_drak.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/General UI/Input/text_field_bg_focused.imageset/text_field_bg_focused_drak.pdf
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Input/text_field_bg_normal.imageset/text_field_bg_normal.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/General UI/Input/text_field_bg_normal.imageset/text_field_bg_normal.pdf
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Input/text_field_bg_normal.imageset/text_field_bg_normal_drak.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/General UI/Input/text_field_bg_normal.imageset/text_field_bg_normal_drak.pdf
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Navigation/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Navigation/nav_back.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "back.pdf",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "template"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Navigation/nav_back.imageset/back.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/General UI/Navigation/nav_back.imageset/back.pdf
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Navigation/nav_back_layoutfix.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "back_layoutfiix.pdf",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "template"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/Navigation/nav_back_layoutfix.imageset/back_layoutfiix.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/General UI/Navigation/nav_back_layoutfix.imageset/back_layoutfiix.pdf
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/avatar_placeholder.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "avatar_placeholder.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/General UI/avatar_placeholder.imageset/avatar_placeholder.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/General UI/avatar_placeholder.imageset/avatar_placeholder.pdf
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Icon/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "provides-namespace" : true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Icon/test16.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "test_icon_16.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "test_icon_16@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "test_icon_16@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Icon/test16.imageset/test_icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/Icon/test16.imageset/test_icon_16.png
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Icon/test16.imageset/test_icon_16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/Icon/test16.imageset/test_icon_16@2x.png
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Icon/test16.imageset/test_icon_16@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/Icon/test16.imageset/test_icon_16@3x.png
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Icon/test24.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "test_icon_24.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "test_icon_24@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "test_icon_24@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Icon/test24.imageset/test_icon_24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/Icon/test24.imageset/test_icon_24.png
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Icon/test24.imageset/test_icon_24@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/Icon/test24.imageset/test_icon_24@2x.png
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/Icon/test24.imageset/test_icon_24@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/Icon/test24.imageset/test_icon_24@3x.png
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/List/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/List/list_accessory10.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "list_accessory.pdf",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "template"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/List/list_accessory10.imageset/list_accessory.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/List/list_accessory10.imageset/list_accessory.pdf
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/List/row_highlight.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "row_highlight.png"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/List/row_highlight.imageset/row_highlight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/List/row_highlight.imageset/row_highlight.png
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/List/table_refresh_indicator.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "list_refresh_indicator.pdf",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | },
21 | "properties" : {
22 | "template-rendering-intent" : "template"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/App/Assets/Assets.xcassets/List/table_refresh_indicator.imageset/list_refresh_indicator.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Assets/Assets.xcassets/List/table_refresh_indicator.imageset/list_refresh_indicator.pdf
--------------------------------------------------------------------------------
/App/Assets/Base.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 |
2 | //"CFBundleDisplayName" = "去重命名";
3 |
--------------------------------------------------------------------------------
/App/Assets/README.md:
--------------------------------------------------------------------------------
1 | # Assets 目录
2 |
3 | 资源文件目录。如果有 Asset Catalog (xcassets)不能管理的资源文件,请把它跟相关模块的文件放在一起,实在不知道放哪才扔到这。
4 |
5 | Tips: 如果 build 时处理 xcassets 耗时很久,可以尝试分成多组 xcassets,可以加速打包速度。
6 |
--------------------------------------------------------------------------------
/App/Development Content/Playground.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/App/Development Content/README.md:
--------------------------------------------------------------------------------
1 | # Development Content 目录
2 |
3 | 在这个目录下的文件供开发阶段测试用,不会包含在正式包中。
4 |
5 | 另见:[GPX 测试数据](https://github.com/BB9z/iOS-Project-Template/wiki/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97#gpx),[Data Package 测试数据](https://github.com/BB9z/iOS-Project-Template/wiki/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97#data-package)
6 |
--------------------------------------------------------------------------------
/App/Development Content/UI Gallery.playground/Pages/RippleView.xcplaygroundpage/Resources/sample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/App/Development Content/UI Gallery.playground/Pages/RippleView.xcplaygroundpage/Resources/sample.png
--------------------------------------------------------------------------------
/App/Development Content/UI Gallery.playground/Pages/TOC.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 |
2 | /*:
3 | # UI Gallery
4 |
5 | 这里展示一些控件的使用,也用来辅助控件开发
6 |
7 | 开启 Playground 设置里的 Build Active Scheme 和 Import App Types 即可快速测试应用里的类型
8 |
9 | ## 目录
10 |
11 | - [TagView](TagView) - 自定义绘制,展示若干 tag
12 | - [RippleView](RippleView) - CAReplicatorLayer 使用 demo,波纹效果
13 |
14 | ----
15 |
16 | [Next](@next)
17 | */
18 |
--------------------------------------------------------------------------------
/App/Development Content/UI Gallery.playground/Pages/TagView.xcplaygroundpage/Contents.swift:
--------------------------------------------------------------------------------
1 |
2 | /*:
3 | [目录](TOC) | [Previous](@previous) | [Next](@next)
4 | */
5 |
6 | import PlaygroundSupport
7 |
8 | class TagItem: TagViewElement {
9 | var title: String
10 |
11 | init(title: String) {
12 | self.title = title
13 | }
14 | }
15 |
16 | PlaygroundPage.current.liveView = {
17 | let tagview = TagView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
18 | tagview.backgroundColor = nil
19 | tagview.isOpaque = false
20 | tagview.items = [
21 | TagItem(title: "title 01"),
22 | TagItem(title: "title very very very very very very very long"),
23 | TagItem(title: "t"),
24 | TagItem(title: "title 04"),
25 | ]
26 | return tagview
27 | }()
28 |
--------------------------------------------------------------------------------
/App/Development Content/UI Gallery.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/App/Development Content/UI Gallery.playground/timeline.xctimeline:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/App/General/Effect/CornerRadius.swift:
--------------------------------------------------------------------------------
1 | // @MBDependency:4
2 | /**
3 | UIView 圆角裁切
4 | */
5 | extension UIView {
6 | @IBInspectable var cornerRadius: CGFloat {
7 | get {
8 | layer.cornerRadius
9 | }
10 | set {
11 | // 不要设置裁切属性,显示圆角不是必须,而且不要裁切时不好撤销设置
12 | layer.cornerRadius = newValue
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/App/General/Effect/MBAutoFadeoutView.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBAutoFadeoutView
3 |
4 | Copyright © 2018 RFUI.
5 | Copyright © 2016 Beijing ZhiYun ZhiYuan Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | #import
13 | #import
14 |
15 | // @MBDependency:2
16 | /**
17 | 显示出来后,默认一段时间隐藏
18 |
19 | 只修改 alpha,不修改 hidden
20 | */
21 | @interface MBAutoFadeoutView : UIView <
22 | RFInitializing
23 | >
24 |
25 | /// 指定显示出来后多久自动隐藏,默认 3s
26 | @property (nonatomic) IBInspectable double fadeoutDelay;
27 |
28 | /// 显示时的 alpha,默认 1
29 | @property (nonatomic) IBInspectable CGFloat showAlpha;
30 |
31 | /// 隐藏时的 alpha,默认 0
32 | @property (nonatomic) IBInspectable CGFloat hideAlpha;
33 |
34 | /// 显隐动画时间,默认 0.3s
35 | @property (nonatomic) IBInspectable double fadeAnimationDuration;
36 |
37 | ///
38 | - (void)setHidden:(BOOL)hidden animated:(BOOL)animated completion:(void (^_Nullable)(BOOL finished))completion;
39 |
40 | @end
41 |
--------------------------------------------------------------------------------
/App/General/Effect/MBIndefiniteRotationImageView.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBIndefiniteRotationImageView
3 |
4 | Copyright © 2018, 2020 RFUI.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 |
11 | #import
12 |
13 | // @MBDependency:2
14 | /**
15 | 无限旋转的 image view
16 |
17 | UIImageView 自带的 isAnimating、startAnimating()、stopAnimating() 已支持
18 | */
19 | @interface MBIndefiniteRotationImageView : UIImageView
20 |
21 | /// 可以控制动画停止
22 | /// 初始化后默认播放动画
23 | @property (nonatomic) IBInspectable BOOL animationStopped;
24 |
25 | /// 设置逆时针旋转,默认 NO 顺时针
26 | @property IBInspectable BOOL counterClockwiseDirection;
27 |
28 | #if TARGET_INTERFACE_BUILDER
29 | @property IBInspectable double rotateDuration;
30 | #else
31 | /// 动画时间,动画已开始设置不会自动更新动画,停止后再开启才会生效
32 | @property NSTimeInterval rotateDuration;
33 | #endif
34 |
35 | @end
36 |
--------------------------------------------------------------------------------
/App/General/Effect/MBMaskHiddenView.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBMaskHiddenView
3 |
4 | Copyright © 2020 RFUI.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 |
11 | #import
12 |
13 | // @MBDependency:2
14 | /**
15 | 显示隐藏时执行 mask 遮罩动画,快速频繁调用有着良好的处理
16 |
17 | 默认隐藏
18 | */
19 | @interface MBMaskHiddenView : UIView
20 |
21 | /// 动画方向,默认 0 向上隐藏,1 向下隐藏
22 | @property IBInspectable NSInteger transitionDirection;
23 |
24 | - (void)setHidden:(BOOL)hidden animated:(BOOL)animated;
25 |
26 | @end
27 |
--------------------------------------------------------------------------------
/App/General/Extensions/Format/Date+Server.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 服务器日期转换
3 | */
4 |
5 | extension Date {
6 | /// 从服务器日期创建
7 | init?(serverString: String?) {
8 | guard let str = serverString,
9 | let date = DateFormatter.server.date(from: str) else { return nil }
10 | self = date
11 | }
12 | }
13 |
14 | extension DateFormatter {
15 | /// 服务器日期格式
16 | static let server: ISO8601DateFormatter = {
17 | let formatter = ISO8601DateFormatter()
18 | formatter.timeZone = .server
19 | return formatter
20 | }()
21 | }
22 |
23 | extension JSONDecoder.DateDecodingStrategy {
24 | /// 服务器日期格式
25 | static let server = JSONDecoder.DateDecodingStrategy.iso8601
26 | }
27 |
28 | extension JSONEncoder.DateEncodingStrategy {
29 | /// 服务器日期格式
30 | static let server = JSONEncoder.DateEncodingStrategy.iso8601
31 | }
32 |
33 | extension TimeZone {
34 | // @MBDependency:2
35 | /// 服务器时区
36 | static var server = TimeZone(identifier: "Asia/Shanghai")!
37 | }
38 |
--------------------------------------------------------------------------------
/App/General/Extensions/Format/DateFormatter+App.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 应用级别的便捷方法
3 | */
4 | extension DateFormatter {
5 |
6 | /// MBDateDayIdentifier 专用格式化
7 | static let dayIdentifier: DateFormatter = {
8 | let formatter = DateFormatter()
9 | formatter.timeZone = .server
10 | formatter.dateFormat = "yyyyMMdd"
11 | return formatter
12 | }()
13 |
14 | /// 本地化的 X年X月X日
15 | static let localizedYMD: DateFormatter = {
16 | DateFormatter.currentLocale(fromTemplate: "yMMMMd")
17 | }()
18 |
19 | /// 本地化的 X月X日
20 | static let localizedMD: DateFormatter = {
21 | DateFormatter.currentLocale(fromTemplate: "MMMMd")
22 | }()
23 |
24 | /// 本地化的周几
25 | static let localizedShortWeek: DateFormatter = {
26 | DateFormatter.currentLocale(fromTemplate: "EEE")
27 | }()
28 | }
29 |
--------------------------------------------------------------------------------
/App/General/Extensions/Foundation/AttributedString+App.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 应用级别的便捷方法:NSAttributedString 扩展
3 | */
4 | extension NSMutableAttributedString {
5 |
6 | /**
7 | 便捷组装方法
8 | */
9 | @discardableResult func append(_ text: String, font: UIFont? = nil, color: UIColor? = nil) -> Self {
10 | var atts = [NSAttributedString.Key: Any]()
11 | if let font = font {
12 | atts[.font] = font
13 | }
14 | if let color = color {
15 | atts[.foregroundColor] = color
16 | }
17 | append(NSAttributedString(string: text, attributes: atts))
18 | return self
19 | }
20 |
21 | /**
22 | 便捷组装方法
23 | */
24 | @discardableResult func append(_ text: String, _ attributes: [NSAttributedString.Key: Any]) -> Self {
25 | append(NSAttributedString(string: text, attributes: attributes))
26 | return self
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/App/General/Extensions/Foundation/Geometry+App.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 应用级别的便捷方法:几何相关扩展
3 | */
4 |
5 | extension CGRect {
6 | /// 矩形中心点
7 | var center: CGPoint {
8 | CGPoint(x: midX, y: midY)
9 | }
10 | }
11 |
12 | extension UIEdgeInsets {
13 | /// 使用各个方向相同的边距创建
14 | /// - Parameter edge: 边距大小
15 | init(edge: CGFloat) {
16 | self.init(top: edge, left: edge, bottom: edge, right: edge)
17 | }
18 |
19 | func reversed() -> UIEdgeInsets {
20 | UIEdgeInsets(top: -top, left: -left, bottom: -bottom, right: -right)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/App/General/Extensions/Foundation/NSArray+App.h:
--------------------------------------------------------------------------------
1 | /*
2 | NSArray+App
3 |
4 | Copyright © 2018 RFUI.
5 | Copyright © 2016 Beijing ZhiYun ZhiYuan Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import
12 |
13 | @interface NSArray (App)
14 |
15 | // @MBDependency:1
16 | /**
17 | 构建新的历史记录数组,新的历史排在最前面,会去重复
18 |
19 | receiver 是旧的历史
20 |
21 | @param items 新的历史
22 | @param limit 历史记录条数限制,不限制需传入 NSIntegerMax 而不能是 0
23 | */
24 | - (nonnull NSArray *)historyArrayWithNewItems:(nullable NSArray *)items limit:(NSUInteger)limit;
25 |
26 | @end
27 |
28 |
29 | @interface NSMutableArray (App)
30 |
31 | // @MBDependency:2
32 | /**
33 | 安全地替换数组中指定位置的元素
34 |
35 | index 越界或者 anObject 为空时不进行任何操作
36 | */
37 | - (void)rf_replaceObjectAtIndex:(NSUInteger)index withObject:(nullable id)anObject;
38 |
39 | // @MBDependency:3
40 | /**
41 | 将数组元素从一个位置移动另一个位置
42 | */
43 | - (void)moveObjectAtIndex:(NSUInteger)fromIndex toIndex:(NSUInteger)toIndex;
44 |
45 | @end
46 |
--------------------------------------------------------------------------------
/App/General/Extensions/Foundation/UIKit+App.h:
--------------------------------------------------------------------------------
1 | /*
2 | UIKit+App
3 | */
4 |
5 | /**
6 | 全局引用常用扩展
7 | */
8 |
9 | #import
10 | #import
11 | #import
12 |
13 | #pragma mark -
14 |
15 | #if __has_include("NSArray+App.h")
16 | # import "NSArray+App.h"
17 | #endif
18 |
19 | #if !TARGET_OS_WATCH
20 |
21 | #import "UIViewController+App.h"
22 |
23 | #endif // END: !TARGET_OS_WATCH
24 |
--------------------------------------------------------------------------------
/App/General/Extensions/Foundation/URL+App.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 应用级别的便捷方法:URL 扩展
3 | */
4 |
5 | extension URL {
6 |
7 | // @MBDependency:2
8 | /// URL 是否是 HTTP 协议的
9 | var isHTTP: Bool {
10 | guard let aScheme = scheme?.lowercased() else { return false }
11 | return aScheme == "https" || aScheme == "http"
12 | }
13 | }
14 |
15 | /*
16 | 沙箱内 URL 每次启动变化解决,参考
17 | https://github.com/BB9z/iOS-Project-Template/blob/4.1/App/General/Extensions/Foundation/NSURL%2BApp.h#L17-L41
18 | */
19 |
--------------------------------------------------------------------------------
/App/General/Extensions/UI App/TextField+Action.swift:
--------------------------------------------------------------------------------
1 | /*
2 | UITextField 操作扩展
3 |
4 | 一般 text field 两边根据需求不同可能会附加一些按钮,比如自定义外观的 x,密码切换的按钮,建议的做法是:
5 |
6 | 使用 MBTextField 的 contentAccessoryView 或 UITextField 本身的 leftView、rightView 添加额外的按钮,这些按钮可以直接连接下面的操作。
7 | */
8 | extension UITextField {
9 |
10 | // @MBDependency:3
11 | /// 清空输入框
12 | @IBAction private func clearText(_ sender: Any?) {
13 | text = nil
14 | sendActions(for: .editingChanged)
15 | }
16 |
17 | // @MBDependency:3
18 | /// 切换密码显示
19 | @IBAction private func togglePasswordDisplay(_ sender: Any?) {
20 | isSecureTextEntry.toggle()
21 | if let c = sender as? UIControl {
22 | c.isSelected = !isSecureTextEntry
23 | }
24 |
25 | // 修正模式切换改变后,光标的位置
26 | if isFirstResponder {
27 | let str = text
28 | let orgRange = selectedTextRange
29 | resignFirstResponder()
30 | becomeFirstResponder()
31 | text = str
32 | selectedTextRange = orgRange
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/App/General/Extensions/UI System/CoreAnimation+App.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 应用级别的便捷方法: CoreAnimation 扩展
3 | */
4 |
5 | extension CATransaction {
6 | /// 无 CA 动画执行一些操作(防止隐式动画)
7 | class func withoutAnimation(_ actions: () -> Void) {
8 | begin()
9 | setDisableActions(true)
10 | actions()
11 | commit()
12 | }
13 | }
14 |
15 | extension CAAnimation: CAAnimationDelegate {
16 |
17 | fileprivate class Delegate: NSObject, CAAnimationDelegate {
18 | var complateBlock: ((Bool) -> Void)?
19 |
20 | func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
21 | if let cb = complateBlock {
22 | cb(flag)
23 | }
24 | }
25 | }
26 |
27 | /// 设置动画完成回调
28 | var complateBlock: ((Bool) -> Void)? {
29 | get {
30 | (delegate as? Delegate)?.complateBlock
31 | }
32 | set {
33 | let obj: Delegate = (delegate as? Delegate) ?? {
34 | let this = Delegate()
35 | delegate = this
36 | return this
37 | }()
38 | obj.complateBlock = newValue
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/App/General/Extensions/UI System/UIApplication+App.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 应用级别的便捷方法: UIApplication 扩展
3 | */
4 | extension UIApplication {
5 |
6 | /// 打开应用设置
7 | static func openSettings() {
8 | guard let url = URL(string: UIApplication.openSettingsURLString) else {
9 | return
10 | }
11 | UIApplication.shared.open(url, options: [:], completionHandler: nil)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/App/General/Extensions/UI System/UIButton+App.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 应用级别的便捷方法
3 | */
4 | extension UIButton {
5 |
6 | // @MBDependency:3
7 | /**
8 | 便捷读取/设置当前状态的文字
9 |
10 | 为了和 UILabel 等控件的设置方式一致
11 |
12 | 注意:设置时如果当前状态的文字未设置,则修改默认状态的文字;这么做符合多数情况下的意图
13 | */
14 | @objc var text: String? {
15 | get {
16 | currentTitle
17 | }
18 | set {
19 | if title(for: state) == nil {
20 | // 文字未设置,设置默认
21 | setTitle(newValue, for: .normal)
22 | return
23 | }
24 | setTitle(newValue, for: state)
25 |
26 | // 状态包含点击态,需要设置排除点击态的样式
27 | guard state.contains(.highlighted) else {
28 | return
29 | }
30 | var stateWithoutHighlited = state
31 | stateWithoutHighlited.remove(.highlighted)
32 | if !stateWithoutHighlited.isEmpty {
33 | setTitle(newValue, for: stateWithoutHighlited)
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/App/General/Extensions/UI System/UIKit+Compatibility.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 系统组件向前兼容声明
3 | */
4 |
5 | extension UIActivityIndicatorView.Style {
6 | init(medium fallbackToGray: Bool) {
7 | if #available(iOS 13.0, *) {
8 | self = .medium
9 | } else {
10 | self = fallbackToGray ? .gray : .white
11 | }
12 | }
13 |
14 | static var big: Self {
15 | if #available(iOS 13.0, *) {
16 | return .large
17 | } else {
18 | return .whiteLarge
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/App/General/Extensions/UI System/UIViewController+App.h:
--------------------------------------------------------------------------------
1 | /*
2 | UIViewController+App
3 |
4 | Copyright © 2018, 2021 BB9z.
5 | Copyright © 2016 Beijing ZhiYun ZhiYuan Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | #import "UIKit+App.h"
13 |
14 | @interface UIViewController (App)
15 |
16 | // @MBDependency:4
17 | /**
18 | 安全的 presentViewController,仅当当前 vc 是导航中可见的 vc 时才 present
19 |
20 | @param completion presented 参数代表给定 vc 是否被弹出
21 | */
22 | - (void)RFPresentViewController:(nonnull UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^ __nullable)(BOOL presented))completion;
23 |
24 | @end
25 |
--------------------------------------------------------------------------------
/App/General/Extensions/UI System/UIViewController+App.m:
--------------------------------------------------------------------------------
1 |
2 | #import "UIViewController+App.h"
3 | #import
4 |
5 | @implementation UIViewController (App)
6 |
7 | - (void)RFPresentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(BOOL presented))completion {
8 | UINavigationController *nav = self.navigationController;
9 | if (!nav) {
10 | NSLog(@"⚠️ 当前 vc 不在导航中,RFPresent 只支持处于导航中的 vc 管理");
11 | if (completion) {
12 | completion(NO);
13 | }
14 | return;
15 | }
16 | UIViewController *navVisible = nav.visibleViewController;
17 | BOOL isNavVisible = NO;
18 | UIViewController *vc = self;
19 | while (vc) {
20 | if (vc == navVisible) {
21 | isNavVisible = YES;
22 | break;
23 | }
24 | vc = vc.parentViewController;
25 | }
26 | if (!self.isViewAppeared
27 | || !isNavVisible) {
28 | if (completion) {
29 | completion(NO);
30 | }
31 | return;
32 | }
33 | [nav presentViewController:viewControllerToPresent animated:flag completion:^{
34 | if (completion) {
35 | completion(YES);
36 | }
37 | }];
38 | }
39 |
40 | @end
41 |
--------------------------------------------------------------------------------
/App/General/Extensions/UI System/UIViewController+App.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 应用级别的便捷方法: View Controller 扩展
3 | */
4 | extension UIViewController {
5 |
6 | /// 用系统弹窗提示一段信息
7 | func alert(title: String?, message: String?, buttonTitle: String = "知道了") {
8 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
9 | alert.addAction(UIAlertAction(title: buttonTitle, style: .cancel, handler: nil))
10 | rfPresent(alert, animated: true, completion: nil)
11 | }
12 |
13 | /// 用系统弹窗提示权限方面的问题,用户可选择跳转到应用设置
14 | func permissionPrompt(title: String?, message: String?) {
15 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
16 | alert.addAction(UIAlertAction(title: "取消", style: .default, handler: nil))
17 | alert.addAction(UIAlertAction(title: "去设置", style: .default, handler: { _ in
18 | UIApplication.openSettings()
19 | }))
20 | rfPresent(alert, animated: true, completion: nil)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/App/General/Form/Picker/MBDatePickerViewController.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBDatePickerViewController
3 |
4 | Copyright © 2018, 2020 RFUI.
5 | Copyright © 2016 Beijing ZhiYun ZhiYuan Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import "MBModalPresentSegue.h"
12 |
13 | // @MBDependency:2
14 | /**
15 | 时间选择弹窗,使用 UIDatePicker
16 |
17 | 备忘:使用实例的 - presentFromViewController:animated:completion: 方法弹出
18 | */
19 | @interface MBDatePickerViewController : MBModalPresentViewController
20 | @property (nonatomic, nullable, weak) IBOutlet UIDatePicker *datePicker;
21 |
22 | #pragma mark - 设置
23 |
24 | /// 调用后清除
25 | @property (nonatomic, nullable, copy) void (^datePickerConfiguration)(UIDatePicker *__nonnull datePicker);
26 |
27 | /// 选择结果的回调
28 | @property (nonatomic, nullable, copy) void (^didEndSelection)(UIDatePicker *__nonnull datePicker, BOOL canceled);
29 |
30 | @end
31 |
--------------------------------------------------------------------------------
/App/General/Form/Picker/MBDatePickerViewController.m:
--------------------------------------------------------------------------------
1 |
2 | #import "MBDatePickerViewController.h"
3 |
4 | @interface MBDatePickerViewController ()
5 | @end
6 |
7 | @implementation MBDatePickerViewController
8 |
9 | - (void)viewDidLoad {
10 | [super viewDidLoad];
11 | if (self.datePickerConfiguration) {
12 | self.datePickerConfiguration(self.datePicker);
13 | self.datePickerConfiguration = nil;
14 | }
15 | }
16 |
17 | #pragma mark -
18 |
19 | - (IBAction)onSave:(id)sender {
20 | if (self.didEndSelection) {
21 | self.didEndSelection(self.datePicker, NO);
22 | self.didEndSelection = nil;
23 | }
24 | [self dismissAnimated:YES completion:nil];
25 | }
26 |
27 | - (IBAction)onCancel:(id)sender {
28 | if (self.didEndSelection) {
29 | self.didEndSelection(self.datePicker, YES);
30 | self.didEndSelection = nil;
31 | }
32 | [self dismissAnimated:YES completion:nil];
33 | }
34 |
35 | @end
36 |
--------------------------------------------------------------------------------
/App/General/ImageView/MBHighlightTintImageView.swift:
--------------------------------------------------------------------------------
1 | /*
2 | MBHighlightTintImageView.swift
3 |
4 | Copyright © 2020 BB9z.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 |
11 | /**
12 | 图片主题色根据高亮状态变化的 image view
13 |
14 | 注意:iOS 11-12 第一屏的图片可能还需要用 UIImageView+MBRenderingMode 修正一下
15 | */
16 | class MBHighlightTintImageView: UIImageView {
17 |
18 | private var normalTintColor: UIColor?
19 | @IBInspectable var highlightTintColor: UIColor? {
20 | didSet {
21 | if isHighlighted, let color = highlightTintColor {
22 | tintColor = color
23 | }
24 | }
25 | }
26 |
27 | override func tintColorDidChange() {
28 | super.tintColorDidChange()
29 | if isHighlighted {
30 | return
31 | }
32 | normalTintColor = tintColor
33 | }
34 |
35 | override var isHighlighted: Bool {
36 | didSet {
37 | guard let color = highlightTintColor else {
38 | return
39 | }
40 | if isHighlighted {
41 | tintColor = color
42 | } else {
43 | tintColor = normalTintColor
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/App/General/ImageView/MBSkyImageView.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBSkyImageView
3 |
4 | Copyright © 2018, 2020 RFUI.
5 | Copyright © 2014-2015 Beijing ZhiYun ZhiYuan Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import "MBImageView.h"
12 |
13 | // @MBDependency:1
14 | /**
15 | image view 的内容跟随 scrollView 滚动而滚动,像是吸附在 scrollView 上
16 |
17 | 原理是随着 scrollView 的 contentOffset Y 轴变化而调整 view 的高度
18 | */
19 | @interface MBSkyImageView : MBImageView <
20 | RFInitializing
21 | >
22 |
23 | ///
24 | @property (weak, nullable, nonatomic) IBOutlet UIScrollView *scrollView;
25 |
26 | /// view 高度和 contentOffset 偏移量的调节
27 | @property IBInspectable CGFloat offsetAdjust;
28 |
29 | /// view 最小高度
30 | @property IBInspectable CGFloat minimalHeight;
31 |
32 | /// 距父 view 底部的距离保持不变
33 | @property IBInspectable BOOL resizeTowardsTop;
34 | @end
35 |
--------------------------------------------------------------------------------
/App/General/ImageView/UIImageView+MBRenderingMode.h:
--------------------------------------------------------------------------------
1 | /*
2 | UIImageView+MBRenderingMode
3 |
4 | Copyright © 2018, 2020 RFUI.
5 | Copyright © 2015 Beijing ZhiYun ZhiYuan Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import
12 |
13 | /**
14 | 从 iOS 7 开始,系统就支持将图片按照给的颜色渲染,
15 | 直到 iOS 13,image view 对 tint color 的处理才完全正确。
16 |
17 | iOS 11+ 的具体问题是:
18 | * iOS 11-12,nib 中设置的 tint color 在启动后第一个页面无效
19 | * iOS 11-12,tintColor 需要设置一个跟上次不同的色值才会有效
20 | */
21 | @interface UIImageView (MBRenderingMode)
22 |
23 | // @MBDependency:2
24 | /**
25 | 设置时强制将图片按 UIImageRenderingModeAlwaysTemplate 方式渲染
26 |
27 | 不会影响以后设置 image 和其它相关属性
28 | */
29 | @property (nonatomic) IBInspectable BOOL renderingAsTemplate;
30 | @end
31 |
--------------------------------------------------------------------------------
/App/General/ImageView/UIImageView+MBRenderingMode.m:
--------------------------------------------------------------------------------
1 |
2 | #import "UIImageView+MBRenderingMode.h"
3 |
4 | @implementation UIImageView (MBRenderingMode)
5 |
6 | - (BOOL)renderingAsTemplate {
7 | NSLog(@"⚠️ 访问 renderingAsTemplate 的 getter 无意义,伪属性,只在 set 时更新一下图片");
8 | return NO;
9 | }
10 |
11 | - (void)setRenderingAsTemplate:(BOOL)renderingAsTemplate {
12 | if (!renderingAsTemplate) return;
13 |
14 | UIImage *image = self.image;
15 | if (image.renderingMode != UIImageRenderingModeAlwaysTemplate) {
16 | self.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
17 | }
18 | else {
19 | // iOS 8-12 都需要重设一下
20 | // REF: http://stackoverflow.com/a/30741478/945906
21 | UIColor *tintColor = self.tintColor;
22 | self.tintColor = nil;
23 | self.tintColor = tintColor;
24 | }
25 | }
26 |
27 | @end
28 |
--------------------------------------------------------------------------------
/App/General/Layout/Auto Layout Fix/MBFixWidthImageView.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBFixWidthImageView
3 |
4 | Copyright © 2018-2019 RFUI.
5 | Copyright © 2015-2016 Beijing ZhiYun ZhiYuan Information Technology Co., Ltd.
6 | Copyright © 2014 Chinamobo Co., Ltd.
7 | https://github.com/BB9z/iOS-Project-Template
8 |
9 | The MIT License
10 | https://opensource.org/licenses/MIT
11 | */
12 | #import
13 |
14 | // @MBDependency:3
15 | /**
16 | 等比例显示的,高度自适应的 image view
17 |
18 | Auto layout 下,image view 的 intrinsicContentSize 会与图像尺寸保持一致,当图像尺寸较小时,一切都很好。
19 | 但当图像被压缩时,比如宽度受限且 contentMode 是 UIViewContentModeScaleAspectFit 时,高度依旧是原始尺寸,但宽度被压缩了,视图上下就会留有空白。这个类就在这方面进行了优化。
20 | */
21 | @interface MBFixWidthImageView : UIImageView
22 |
23 | /**
24 | 未设置图片时默认的高宽比
25 | */
26 | @property IBInspectable CGFloat defaultSizeRatio;
27 |
28 | /**
29 | 未设置图片时返回 no intrinsic size
30 |
31 | 优先级低于 defaultSizeRatio
32 | */
33 | @property IBInspectable BOOL perfersNoIntrinsicMetric;
34 | @end
35 |
--------------------------------------------------------------------------------
/App/General/Layout/MBCellStackView.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBCellStackView
3 |
4 | Copyright © 2018-2019 RFUI.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 | #import
11 | #import
12 |
13 | // @MBDependency:1
14 | /**
15 | 复用填充一组 view
16 |
17 | 在 Swift 中需要用 typealias 声明一下,直接带 generic type IB 的表现会异常
18 | */
19 | @interface MBCellStackView : UIStackView <
20 | RFInitializing
21 | >
22 |
23 | @property (nullable, nonatomic) NSArray *items;
24 | @property (nullable) UINib *cellNib;
25 |
26 | /// 设置后即清空,仅用于更新 cellNib
27 | @property (nullable) IBInspectable NSString *cellNibName;
28 |
29 | /// 如未设置,尝试在 cell 上执行 setItem:
30 | @property (nullable, nonatomic) void (^configureCell)(MBCellStackView *__nonnull stackView, ViewType __nonnull cell, NSInteger index, ObjectType __nonnull item);
31 |
32 | @end
33 |
--------------------------------------------------------------------------------
/App/General/Layout/MBDesignView.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBDesignView
3 |
4 | Copyright © 2018 RFUI.
5 | Copyright © 2015 Beijing ZhiYun ZhiYuan Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import
12 |
13 | // @MBDependency:1
14 | /**
15 | 在 IB 中为了看清浅色元素或者为了标记区域,可以给 view 设置背景色,运行时再清空
16 |
17 | 这个 view 会在 awakeFromNib 时自动清空背景色
18 | */
19 | @interface MBDesignView : UIView
20 | @end
21 |
--------------------------------------------------------------------------------
/App/General/Layout/MBDesignView.m:
--------------------------------------------------------------------------------
1 |
2 | #import "MBDesignView.h"
3 |
4 | @implementation MBDesignView
5 |
6 | - (void)awakeFromNib {
7 | [super awakeFromNib];
8 | self.backgroundColor = nil;
9 | }
10 |
11 | @end
12 |
--------------------------------------------------------------------------------
/App/General/Layout/MBFlipCollapsibleView.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBFlipCollapsibleView
3 |
4 | Copyright © 2018 RFUI.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 |
11 | #import
12 | #import
13 | #import
14 |
15 | // @MBDependency:1
16 | /**
17 | 利用 layer mask 展开收起的 view
18 | 收起后需要外部手动设置隐藏
19 | */
20 | @interface MBFlipCollapsibleView : UIView <
21 | RFInitializing
22 | >
23 |
24 | /// 收起的朝向
25 | /// 目前只支持 top、bottom;默认 top
26 | @property (nonatomic) RFResizeAnchor direction;
27 |
28 | /// 默认 YES
29 | @property (nonatomic) IBInspectable BOOL expand;
30 | @end
31 |
--------------------------------------------------------------------------------
/App/General/Layout/MBLayoutConstraint.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBLayoutConstraint
3 |
4 | Copyright © 2018 RFUI.
5 | Copyright © 2014 Beijing ZhiYun ZhiYuan Information Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import
12 |
13 | // @MBDependency:2
14 | /**
15 | 增加了折叠展开支持
16 | */
17 | @interface MBLayoutConstraint : NSLayoutConstraint
18 |
19 | @property (nonatomic) IBInspectable BOOL expand;
20 | - (void)setExpand:(BOOL)expand animated:(BOOL)animated;
21 |
22 | /**
23 | 折叠起来的约束量
24 | */
25 | @property (nonatomic) IBInspectable CGFloat contractedConstant;
26 |
27 | /**
28 | 展开的约束量
29 |
30 | 如未设置(为0),将在 awakeFromNib 时设置为当前约束量
31 | */
32 | @property (nonatomic) IBInspectable CGFloat expandedConstant;
33 | @end
34 |
--------------------------------------------------------------------------------
/App/General/Layout/MBLayoutConstraint.m:
--------------------------------------------------------------------------------
1 |
2 | #import "MBLayoutConstraint.h"
3 | #import
4 |
5 | @interface MBLayoutConstraint ()
6 | @end
7 |
8 | @implementation MBLayoutConstraint
9 |
10 | - (void)awakeFromNib {
11 | [super awakeFromNib];
12 |
13 | if (self.constant && !self.expandedConstant) {
14 | self.expandedConstant = self.constant;
15 | self.expand = YES;
16 | }
17 | }
18 |
19 | - (void)setExpand:(BOOL)expand {
20 | _expand = expand;
21 | self.constant = expand? self.expandedConstant : self.contractedConstant;
22 | }
23 |
24 | - (void)setContractedConstant:(CGFloat)contractedConstant {
25 | _contractedConstant = contractedConstant;
26 | if (!self.expand) {
27 | self.constant = contractedConstant;
28 | }
29 | }
30 |
31 | - (void)setExpandedConstant:(CGFloat)expandedConstant {
32 | _expandedConstant = expandedConstant;
33 | if (self.expand) {
34 | self.constant = expandedConstant;
35 | }
36 | }
37 |
38 | - (void)setExpand:(BOOL)expand animated:(BOOL)animated {
39 | self.expand = expand;
40 |
41 | if (animated) {
42 | [UIView animateWithDuration:.3 animations:^{
43 | [self updateLayoutIfNeeded];
44 | }];
45 | }
46 | }
47 |
48 | @end
49 |
--------------------------------------------------------------------------------
/App/General/Layout/MBSceneStackView.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBSceneStackView
3 |
4 | Copyright © 2018 RFUI.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 | #import
11 |
12 | // @MBDependency:2
13 | /**
14 | 基于 UIStackView 的界面切换
15 |
16 | 把 UIStackView 中的 view 分成若干组,每次显示一组,并支持组间切换显示
17 | */
18 | @interface MBSceneStackView : UIStackView
19 |
20 | @property (readonly) NSInteger activeSceneIndex;
21 | @property (nonatomic, nullable) NSArray *> *scenes;
22 |
23 | - (void)setActiveSceneWithIndex:(NSInteger)index animated:(BOOL)animated;
24 |
25 | - (void)nextSceneAnimated:(BOOL)animated;
26 | - (void)previousSceneAnimated:(BOOL)animated;
27 |
28 | @property (nullable) void (^onSceneChanged)(MBSceneStackView *__nonnull stackView, NSInteger activeSceneIndex);
29 |
30 | @end
31 |
--------------------------------------------------------------------------------
/App/General/List/CollectionView/Layout/MBCollectionViewCarouselLayout.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBCollectionViewCarouselLayout
3 |
4 | Copyright © 2018, 2020 RFUI.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 |
11 | #import "MBCollectionViewFlowLayout.h"
12 |
13 | // @MBDependency:2
14 | /**
15 | 像 iCarousel 线性的效果
16 |
17 | 内部做了什么:
18 | - 通过调整 sectionInset,使列表头尾可以自然对齐中心
19 | - 处理滚动的终止位置,以使 cell 始终对齐中心
20 |
21 | */
22 | @interface MBCollectionViewCarouselLayout : MBCollectionViewFlowLayout
23 |
24 | /**
25 | 非空时代表的是 cell 与 collection view 边缘的距离,此时 itemSize 会根据 collection view 的大小做调整
26 | */
27 | @property (nonatomic) IBInspectable CGFloat fixedCellPadding;
28 |
29 | @end
30 |
--------------------------------------------------------------------------------
/App/General/List/CollectionView/Layout/MBCollectionViewEqualColumnSpaceLayout.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBCollectionViewEqualColumnSpaceLayout
3 |
4 | Copyright © 2018-2019 RFUI.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 |
11 | #import "MBCollectionViewFlowLayout.h"
12 |
13 | typedef NS_ENUM(NSInteger, MBCollectionViewColumnLayoutStyle) {
14 | /// sectionInset 的左右和 minimumInteritemSpacing 相同
15 | MBCollectionViewColumnLayoutStyleSectionInsetEqualItemSpacing = 0,
16 | /// 整个宽度按列数等分,cell 位于每块区域的中心
17 | MBCollectionViewColumnLayoutStyleCenter,
18 | /// 左右无边距的均分
19 | MBCollectionViewColumnLayoutStyleNoSectionInset,
20 | };
21 |
22 | // @MBDependency:2
23 | /**
24 | 把 collectionView 分成给定列数并对齐
25 | */
26 | @interface MBCollectionViewEqualColumnSpaceLayout : MBCollectionViewFlowLayout
27 |
28 | /// 列数
29 | @property (nonatomic) IBInspectable NSUInteger numberOfColumns;
30 |
31 | #if TARGET_INTERFACE_BUILDER
32 | @property (nonatomic) IBInspectable NSInteger layoutStyle;
33 | #else
34 | @property (nonatomic) MBCollectionViewColumnLayoutStyle layoutStyle;
35 | #endif
36 |
37 | @end
38 |
--------------------------------------------------------------------------------
/App/General/List/CollectionView/Layout/MBCollectionViewFlowLayout.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBCollectionViewFlowLayout
3 |
4 | Copyright © 2018, 2020 RFUI.
5 | Copyright © 2014 Beijing ZhiYun ZhiYuan Information Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import
12 | #import
13 |
14 | // @MBDependency:3
15 | /**
16 | UICollectionViewFlowLayout 等价体,修正特定版本系统的 bug
17 |
18 | 当前已无任何修正
19 | */
20 | @interface MBCollectionViewFlowLayout : UICollectionViewFlowLayout <
21 | RFInitializing
22 | >
23 |
24 | @end
25 |
--------------------------------------------------------------------------------
/App/General/List/CollectionView/Layout/MBCollectionViewFlowLayout.m:
--------------------------------------------------------------------------------
1 |
2 | #import "MBCollectionViewFlowLayout.h"
3 |
4 | @implementation MBCollectionViewFlowLayout
5 | RFInitializingRootForNSObjectSupportNSCoding
6 |
7 | - (void)onInit {
8 | }
9 |
10 | - (void)afterInit {
11 | }
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/App/General/List/CollectionView/MBCollectionListDisplayer.h:
--------------------------------------------------------------------------------
1 | /*!
2 | MBCollectionListDisplayer
3 |
4 | Copyright © 2020 RFUI.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 |
11 | #import "MBCollectionView.h"
12 | #import "MBGeneralListDisplaying.h"
13 |
14 | // @MBDependency:2
15 | /**
16 | 专用于嵌在其他界面的列表
17 |
18 | 把显示、跳转可以封装在这里
19 | 处理了嵌套 vc 取消请求的问题
20 | */
21 | @interface MBCollectionListDisplayer : UIViewController <
22 | RFInitializing,
23 | MBGeneralListDisplaying
24 | >
25 |
26 | @property (weak, nullable, nonatomic) IBOutlet MBCollectionView *collectionView;
27 |
28 | @property (nullable, nonatomic) IBInspectable NSString *APIName;
29 |
30 | @property (weak, nullable, nonatomic) MBCollectionViewDataSource *dataSource;
31 |
32 | /// 默认不做什么
33 | - (void)setupDataSource:(nonnull MBCollectionViewDataSource *)ds;
34 |
35 | @end
36 |
--------------------------------------------------------------------------------
/App/General/List/CollectionView/MBEntitiesCollectionView.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBEntitiesCollectionView
3 |
4 | Copyright © 2018 RFUI.
5 | Copyright © 2014-2016 Beijing ZhiYun ZhiYuan Information Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | #import
13 | #import
14 |
15 | // @MBDependency:2
16 | /**
17 | 一个简单快速的 collection view 子类:
18 |
19 | - 数组作为单 section 的数据源
20 | - 默认 reuse identifier 为 "Cell"
21 | - Cell 点击时尝试执行 cell 的 onCellSelected 方法
22 | - 默认 scrollsToTop 为 NO
23 |
24 | 在 Swift 中需要用 typealias 声明一下,直接带 generic type IB 的表现会异常
25 | */
26 | @interface MBEntitiesCollectionView : UICollectionView <
27 | RFInitializing,
28 | UICollectionViewDataSource,
29 | UICollectionViewDelegate
30 | >
31 | /// 设置时重载
32 | @property (nonatomic, nullable) NSArray *items;
33 |
34 | /**
35 | 可选 cell 设置 block,默认直接给 item
36 | */
37 | @property (nullable) void (^cellConfigBlock)(__kindof UICollectionViewCell *__nonnull cell, ItemType __nonnull item);
38 |
39 | - (void)appendItem:(nullable ItemType)item;
40 | - (void)removeItem:(nullable ItemType)item;
41 |
42 | @end
43 |
--------------------------------------------------------------------------------
/App/General/List/CollectionView/PullToFetch/MBCollectionRefreshFooterView.h:
--------------------------------------------------------------------------------
1 | /*!
2 | MBCollectionRefreshFooterView
3 |
4 | Copyright © 2018, 2020 BB9z.
5 | Copyright © 2014-2015 Beijing ZhiYun ZhiYuan Information Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import
12 | #import
13 |
14 | // @MBDependency:1
15 | /**
16 | Collection view 的上拉加载下一页
17 | */
18 | @interface MBCollectionRefreshFooterView : UICollectionReusableView
19 |
20 | @property (nonatomic) RFRefreshControlStatus status;
21 |
22 | /**
23 |
24 | 默认点击会通过 Responder chain 执行 onLoadNextPage:
25 | */
26 | @property (weak, nonatomic) IBOutlet UIButton *loadButton;
27 |
28 | #pragma mark - 到底
29 |
30 | @property (readonly, nonatomic) BOOL end;
31 |
32 | @property (weak, nonatomic) IBOutlet UILabel *endLabel;
33 | @property (weak, nonatomic) IBOutlet UIView *endView;
34 | @property (strong, nonatomic) UIView *customEndView;
35 |
36 | #pragma mark - 内容为空
37 |
38 | @property (readonly, nonatomic) BOOL empty;
39 |
40 | @property (weak, nonatomic) IBOutlet UILabel *emptyLabel;
41 | @property (strong, nonatomic) UIView *customEmptyView;
42 | @property (weak, nonatomic) IBOutlet UIView *outerEmptyView;
43 |
44 | @end
45 |
--------------------------------------------------------------------------------
/App/General/List/MBListDateItem.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBListDateItem
3 |
4 | Copyright © 2018 RFUI.
5 | Copyright © 2015-2016 Beijing ZhiYun ZhiYuan Information Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | #import "MBModel.h"
13 |
14 | NS_ASSUME_NONNULL_BEGIN
15 |
16 | // @MBDependency:2
17 | @interface MBListDataItem : NSObject <
18 | MBModel
19 | >
20 |
21 | @property (nonatomic, nullable) ObjectType item;
22 | @property (nonatomic) NSString *cellReuseIdentifier;
23 |
24 | + (instancetype)dataItemWithItem:(nullable ObjectType)item cellReuseIdentifier:(NSString *)identifier;
25 |
26 | @end
27 |
28 | // @MBDependency:2
29 | @interface MBListSectionDataItem : NSObject
30 |
31 | @property (nonatomic, nullable) SectionType sectionItem;
32 | @property (nonatomic) NSString *sectionIndicator;
33 | @property (nonatomic) NSMutableArray *> *rows;
34 |
35 | + (instancetype)dataItemWithSectionItem:(nullable SectionType)sectionItem sectionIndicator:(NSString *)sectionIndicator rows:(NSMutableArray *> *)rows;
36 |
37 | @end
38 |
39 | void MBListDataItemAddToItems(NSString *cellIdentifier, id __nullable item, NSMutableArray *items);
40 |
41 | NS_ASSUME_NONNULL_END
42 |
--------------------------------------------------------------------------------
/App/General/List/TableView/MBEntitiesTableView.h:
--------------------------------------------------------------------------------
1 | /*!
2 | MBEntitiesTableView
3 |
4 | Copyright © 2020 RFUI.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 |
11 | #import
12 | #import
13 |
14 | // @MBDependency:2
15 | /**
16 | 一个简单快速的 table view 子类:
17 |
18 | - 数组作为单 section 的数据源
19 | - 默认 reuse identifier 为 "Cell"
20 | - Cell 点击时尝试执行 cell 的 onCellSelected 方法
21 |
22 | 在 Swift 中需要用 typealias 声明一下,直接带 generic type IB 的表现会异常
23 | */
24 | @interface MBEntitiesTableView : UITableView <
25 | RFInitializing,
26 | UITableViewDataSource,
27 | UITableViewDelegate
28 | >
29 | /// 设置时重载
30 | @property (nonatomic, nullable) NSArray *items;
31 |
32 | /**
33 | 可选 cell 设置 block,默认直接给 item
34 | */
35 | @property (nullable) void (^cellConfigBlock)(__kindof UITableViewCell *__nonnull cell, ItemType __nonnull item);
36 |
37 | - (void)appendItem:(nullable ItemType)item;
38 | - (void)removeItem:(nullable ItemType)item;
39 | @end
40 |
--------------------------------------------------------------------------------
/App/General/List/TableView/MBTableHeaderFooterView.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBTableHeaderFooterView
3 |
4 | Copyright © 2018 RFUI.
5 | Copyright © 2014-2016 Beijing ZhiYun ZhiYuan Information Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import
12 | #import
13 |
14 | // @MBDependency:3
15 | /**
16 | 可以用 AutoLayout 自动调节高度的 tableHeaderView/tableFooterView
17 |
18 | 使用:
19 |
20 | 一般在 IB 中加一个 contentView 作为容器,然后用 AutoLayout 撑开 contentView
21 |
22 | */
23 | @interface MBTableHeaderFooterView : UIView <
24 | RFInitializing,
25 | RFOnlySupportLoadFromNib
26 | >
27 |
28 | /// 子 view 的容器,MBTableHeaderFooterView 的高度会跟它的高度同步
29 | /// 如果不设置,高度不会自行改变
30 | @property (weak, nonatomic) IBOutlet UIView *contentView;
31 |
32 | /// 刷新高度的方法,正常会自动更新,一般不用调用
33 | - (void)updateHeight;
34 | - (void)updateHeightAnimated:(BOOL)animated;
35 |
36 | /// 代码方式安装为 tableHeaderView
37 | - (void)setupAsHeaderViewToTableView:(UITableView *)tableView;
38 |
39 | /// 代码方式安装为 tableFooterView
40 | - (void)setupAsFooterViewToTableView:(UITableView *)tableView;
41 | @end
42 |
--------------------------------------------------------------------------------
/App/General/List/TableView/MBTableViewController.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBTableViewController
3 |
4 | Copyright © 2018 RFUI.
5 | Copyright © 2015 Beijing ZhiYun ZhiYuan Information Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import "MBTableView.h"
12 |
13 | // @MBDependency:3
14 | /**
15 | 比普通 UIViewController + MBTableView 多了以下特性:
16 |
17 | - MBGeneralListDisplaying 协议支持
18 | - tableView:didSelectRowAtIndexPath: 时尝试执行 cell 的 onCellSelected 方法
19 | - 视图显示时取消选中单元
20 | - 适合 table view 的 segue 准备方法
21 | */
22 | @interface MBTableViewController : UIViewController
23 | @property (weak, nonatomic) IBOutlet MBTableView *listView;
24 |
25 | @end
26 |
--------------------------------------------------------------------------------
/App/General/List/TableView/MBTableViewController.m:
--------------------------------------------------------------------------------
1 |
2 | #import "MBTableViewController.h"
3 | #import "MBGeneralCellResponding.h"
4 |
5 | @interface MBTableViewController ()
6 | @end
7 |
8 | @implementation MBTableViewController
9 |
10 | - (void)viewWillAppear:(BOOL)animated {
11 | [super viewWillAppear:animated];
12 | for (NSIndexPath *row in self.listView.indexPathsForSelectedRows) {
13 | [self.listView deselectRowAtIndexPath:row animated:animated];
14 | }
15 | }
16 |
17 | - (void)refresh {
18 | [self.listView.pullToFetchController triggerHeaderProcess];
19 | }
20 |
21 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
22 | id cell = (id)[tableView cellForRowAtIndexPath:indexPath];
23 | if ([cell respondsToSelector:@selector(onCellSelected)]) {
24 | [cell onCellSelected];
25 | }
26 | }
27 |
28 | @end
29 |
--------------------------------------------------------------------------------
/App/General/List/TableView/PullToFetch/MBRefreshFooterView.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBRefreshFooterView
3 |
4 | Copyright © 2018 RFUI.
5 | Copyright © 2015 Beijing ZhiYun ZhiYuan Information Technology Co., Ltd.
6 | Copyright © 2014 Chinamobo Co., Ltd.
7 | https://github.com/BB9z/iOS-Project-Template
8 |
9 | The MIT License
10 | https://opensource.org/licenses/MIT
11 | */
12 | #import
13 |
14 | // @MBDependency:4
15 | @interface MBRefreshFooterView : UIView <
16 | RFInitializing
17 | >
18 | @property (weak, nonatomic) IBOutlet UILabel *emptyLabel;
19 | @property (weak, nonatomic) IBOutlet UILabel *endLabel;
20 | @property (weak, nonatomic) IBOutlet UIView *endView;
21 | @property (weak, nonatomic) IBOutlet UILabel *textLabel;
22 |
23 | /**
24 | 外部的空内容指示视图,如果设置 MBRefreshFooterView 会根据自身 empty 属性变化设置该视图的显隐
25 | */
26 | @property (weak, nonatomic) IBOutlet UIView *outerEmptyView;
27 |
28 | /**
29 | 列表内容为空。
30 |
31 | 置为 YES 将显示 emptyLabel 的内容
32 | */
33 | @property (nonatomic) BOOL empty;
34 |
35 | @property (nonatomic) RFPullToFetchIndicatorStatus status;
36 | - (void)updateStatus:(RFPullToFetchIndicatorStatus)status distance:(CGFloat)distance control:(RFTableViewPullToFetchPlugin *)control;
37 |
38 | @end
39 |
--------------------------------------------------------------------------------
/App/General/List/TableView/PullToFetch/MBRefreshHeaderView.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBRefreshHeaderView
3 |
4 | Copyright © 2018 RFUI.
5 | Copyright © 2015 Beijing ZhiYun ZhiYuan Information Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import
12 |
13 | // @MBDependency:4
14 | @interface MBRefreshHeaderView : UIView
15 | @property (weak, nonatomic) IBOutlet UIView *contentView;
16 | @property (weak, nonatomic) IBOutlet UILabel *statusLabel;
17 | @property (weak, nonatomic) IBOutlet UIImageView *indicatorImageView;
18 | @property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicatorView;
19 |
20 | /**
21 | 外部的空内容指示视图,如果设置 MBRefreshFooterView 会根据自身 empty 属性变化设置该视图的显隐
22 | */
23 | @property (weak, nonatomic) IBOutlet UIView *outerEmptyView;
24 |
25 | /**
26 | 列表内容为空
27 |
28 | 置为 YES 将显示 outerEmptyView
29 | */
30 | @property (nonatomic) BOOL empty;
31 |
32 | @property (nonatomic) RFPullToFetchIndicatorStatus status;
33 | - (void)updateStatus:(RFPullToFetchIndicatorStatus)status distance:(CGFloat)distance control:(RFTableViewPullToFetchPlugin *)control;
34 |
35 | @end
36 |
--------------------------------------------------------------------------------
/App/General/List/TableView/PullToFetch/MBTableViewPullToFetchControl.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBTableViewPullToFetchControl
3 |
4 | Copyright © 2014-2015 Beijing ZhiYun ZhiYuan Information Technology Co., Ltd.
5 | Copyright © 2014 Chinamobo Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import "RFTableViewPullToFetchPlugin.h"
12 | #import "MBRefreshHeaderView.h"
13 | #import "MBRefreshFooterView.h"
14 |
15 | // @MBDependency:4
16 | /**
17 | RFTableViewPullToFetchPlugin 只是处理了下拉、上推的逻辑,没有包含外观。
18 |
19 | 这个类对外观进行了进一步的封装,外观的调整需修改 MBRefreshHeaderView 和 MBRefreshFooterView。
20 | */
21 | @interface MBTableViewPullToFetchControl : RFTableViewPullToFetchPlugin
22 | @property MBRefreshHeaderView *headerContainer;
23 | @property MBRefreshFooterView *footerContainer;
24 | @end
25 |
--------------------------------------------------------------------------------
/App/General/Navigation/MBNavigationBar.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBNavigationBar
3 |
4 | Copyright © 2018 RFUI.
5 | Copyright © 2015-2016 Beijing ZhiYun ZhiYuan Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import
12 | #import
13 |
14 | // @MBDependency:1
15 | @interface MBNavigationBar : UINavigationBar <
16 | RFInitializing
17 | >
18 |
19 | // 原生的导航位置/平铺模式不好控制,如果不能轻易调好,需要很多奇怪的组合的话,往后系统改行为就坑了
20 | // 不如写个自己的便于控制
21 | @property (nonatomic, nullable, weak) UIImageView *customShadowImageView;
22 | @end
23 |
--------------------------------------------------------------------------------
/App/General/Navigation/MBNavigationItem.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBNavigationItem
3 |
4 | Copyright © 2018 RFUI.
5 | Copyright © 2015 Beijing ZhiYun ZhiYuan Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import
12 |
13 | // @MBDependency:2
14 | /**
15 | 可以用另一个 UINavigationItem 设置 MBNavigationItem 的状态,并可以恢复原始状态
16 |
17 | 典型场景:在表单页面,切换到不同的 field 时可能需要导航按钮呈现不同的状态,field 失去焦点时还原原始的状态。这个场景下 vc 使用 MBNavigationItem,不同的 field 关联响应的 UINavigationItem,就很好做。
18 | */
19 | @interface MBNavigationItem : UINavigationItem
20 |
21 | /**
22 | 用 sorceItem 的属性设置 destinationItem
23 | */
24 | + (void)applyNavigationItem:(UINavigationItem *)sorceItem toNavigationItem:(UINavigationItem *)destinationItem animated:(BOOL)animated;
25 |
26 | /// 将 MBNavigationItem 应用成另一个 navigationItem 的样子
27 | - (void)applyNavigationItem:(UINavigationItem *)navigationItem animated:(BOOL)animated;
28 |
29 | /// 还原 MBNavigationItem
30 | - (void)restoreNavigationItemAnimated:(BOOL)animated;
31 |
32 | @end
33 |
--------------------------------------------------------------------------------
/App/General/Navigation/MBNavigationTitleView.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBNavigationTitleView
3 |
4 | Copyright © 2018 RFUI.
5 | Copyright © 2015 Beijing ZhiYun ZhiYuan Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import
12 |
13 | // @MBDependency:1
14 | /**
15 | 设置为 UINavigationItem 的 titleView,在显示出来的时候会填满导航条(除了左右按钮的空间)
16 | */
17 | @interface MBNavigationTitleView : UIView
18 |
19 | /**
20 | 默认行为是填满除了左右按钮的空间,这样的话 UI 可能不会相对于屏幕居中
21 | 把这个属性置为 YES 会保持居中,但空间可能就有多余了
22 | */
23 | @property IBInspectable BOOL keepCenterLayout;
24 | @end
25 |
--------------------------------------------------------------------------------
/App/General/Navigation/MBNavigationTitleView.m:
--------------------------------------------------------------------------------
1 |
2 | #import "MBNavigationTitleView.h"
3 |
4 | @interface MBNavigationTitleView ()
5 | @property BOOL hasLayoutOnce;
6 | @end
7 |
8 | @implementation MBNavigationTitleView
9 |
10 | // 这个类只是用于标识,布局操作是在 MBNavigationBar 中完成的
11 | // 下面这段是辅助布局的,iOS 10 SDK 修改后,导航上布局代码触发的事件太晚了,导致进入界面后会闪一下
12 | - (void)willMoveToWindow:(UIWindow *)newWindow {
13 | [super willMoveToWindow:newWindow];
14 | if (!self.hasLayoutOnce) {
15 | self.hasLayoutOnce = YES;
16 | [self.superview setNeedsLayout];
17 | }
18 | }
19 |
20 | @end
21 |
--------------------------------------------------------------------------------
/App/General/Navigation/MBRoundBarButtonItem.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBRoundBarButtonItem
3 |
4 | Copyright © 2018 RFUI.
5 | Copyright © 2016 Beijing ZhiYun ZhiYuan Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import
12 |
13 | // @MBDependency:1
14 | /**
15 | MBRoundBarButtonItem 与普通 UIBarButtonItem 使用方式一样,运行时会带圆角背景。如果用正常的 UIBarButtonItem 来实现会比较麻烦,IB 中 拖 view 加 UIButton,连线,尺寸还不好调。
16 |
17 | 目前实现比较简单,有需求再扩展:
18 | - 默认背景色由 tintColor 控制,文字默认是白色
19 | - 按钮偏移做了匹配,点击区域做了优化
20 | - 初始尺寸随文字自适应,文字变更的调整没做
21 | - 圆角、字体大小等都是写死的
22 | - 不支持代码创建
23 |
24 | */
25 | @interface MBRoundBarButtonItem : UIBarButtonItem
26 |
27 | /// 从 nib 中载入后设置,供外观定制
28 | @property (readonly, nullable) UIButton *buttonView;
29 | @end
30 |
--------------------------------------------------------------------------------
/App/General/Others/UIAnyGestureRecognizer.h:
--------------------------------------------------------------------------------
1 | /*
2 | UIAnyGestureRecognizer
3 |
4 | Copyright © 2020 RFUI.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 | #import
11 |
12 | // @MBDependency:2
13 | /**
14 | 有触摸即触发的手势识别
15 |
16 | 典型场景:一个弹窗需要识别其他区域的任意触碰以关闭
17 | */
18 | @interface UIAnyGestureRecognizer : UIGestureRecognizer
19 | @end
20 |
--------------------------------------------------------------------------------
/App/General/Others/UIAnyGestureRecognizer.m:
--------------------------------------------------------------------------------
1 |
2 | #import "UIAnyGestureRecognizer.h"
3 | #import
4 |
5 | @implementation UIAnyGestureRecognizer
6 |
7 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
8 | self.state = UIGestureRecognizerStateRecognized;
9 | }
10 |
11 | @end
12 |
--------------------------------------------------------------------------------
/App/General/README.md:
--------------------------------------------------------------------------------
1 | # General 目录
2 |
3 | 这个目录存放整个项目级别的组件,第三方组件优先使用 CocoaPods 引入,如有特殊考虑不用 CocoaPods 引入的,请放到 Frameworks 目录下。
4 |
5 | 如果一个组件只在某一个业务模块下使用,应该跟业务相关的代码一起放在 Scene 下。
6 |
7 | 放在这里的应该是:
8 | * 组件,具有一般性的,不是针对某个特定场景的
9 | * 可能影响整个项目行为的
10 | * 可能存在相互耦合(所以不能成为独立的第三方组件)
11 |
--------------------------------------------------------------------------------
/App/General/ScrollView/MBKeyboardAdjustScrollView.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBKeyboardAdjustScrollView
3 |
4 | Copyright © 2020, 2022 BB9z.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 |
11 | #import
12 |
13 | // @MBDependency:2
14 | /**
15 | 帮助简化键盘处理的 UIScrollView 类
16 | A UIScrollView subclass to simplify keyboard handling
17 |
18 | 它可以自动帮助你:
19 |
20 | - 根据键盘高度调整 content inset,保证键盘出现时所有内容滚动可见(底部不会被键盘遮住)
21 | - 如果获取焦点的输入框在 scroll view 中(无论层级),尝试以最好的效果(考虑选中范围,视图大小)调整可视区域
22 |
23 | It automatically helps you:
24 |
25 | - Adjust content inset according to keyboard height to make all content scrolling visible.
26 | - If any view in this scroll view becomes the first responder. It will try to adjust the scroll position to make it visible with the best effect (considering the selection range, view size)
27 | */
28 | @interface MBKeyboardAdjustScrollView : UIScrollView
29 | @end
30 |
--------------------------------------------------------------------------------
/App/General/Search/MBSearchTextField.h:
--------------------------------------------------------------------------------
1 | /*!
2 | MBSearchTextField
3 |
4 | Copyright © 2018, 2020 RFUI.
5 | Copyright © 2016 Beijing ZhiYun ZhiYuan Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import
12 | #import
13 |
14 | // @MBDependency:2
15 | /**
16 | 搜索输入框
17 | */
18 | @interface MBSearchTextField : UITextField <
19 | RFInitializing
20 | >
21 |
22 | /**
23 | 默认 0.6 s,此处延时
24 | 1. 为过滤输入过程中不需要的请求
25 | 2. 为防止第一次搜索的分页结果覆盖第二次搜索的结果
26 |
27 | 设置为 0 关闭搜索,初始化后变更可能不生效
28 | */
29 | @property IBInspectable double autoSearchTimeInterval;
30 |
31 | /**
32 | 允许自动搜索的关键字最短长度,小于该长度的不自动搜索
33 | */
34 | @property IBInspectable NSUInteger autoSearchMinimumLength;
35 |
36 | /**
37 | 非空时会自动取消相应的请求
38 | */
39 | @property (nullable) IBInspectable NSString *APIName;
40 |
41 | /**
42 | 不允许搜空
43 | */
44 | @property IBInspectable BOOL disallowEmptySearch;
45 |
46 | #pragma mark -
47 |
48 | /**
49 | 执行搜索
50 | */
51 | @property (nullable) void (^doSearch)(NSString *__nullable keyWords, BOOL isAutoSearch);
52 |
53 | /**
54 | 强制立即搜索
55 | */
56 | - (void)doSearchforce;
57 |
58 | /**
59 | 外部搜索结束后需要手动设置为 NO
60 | */
61 | @property (nonatomic) BOOL isSearching;
62 |
63 | @end
64 |
--------------------------------------------------------------------------------
/App/General/Search/MBSearchViewController.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBSearchViewController
3 |
4 | Copyright © 2018 RFUI.
5 | Copyright © 2014-2015, 2017 Beijing ZhiYun ZhiYuan Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import
12 | #import "MBSearchTextField.h"
13 |
14 | // @MBDependency:2
15 | /**
16 | 通用沉浸式搜索界面
17 |
18 | 用来替代 UISearchDisplayController
19 | */
20 | @interface MBSearchViewController : UIViewController <
21 | UISearchBarDelegate
22 | >
23 | @property (nonatomic, weak) IBOutlet MBSearchTextField *searchTextField;
24 | @property (nonatomic, weak) IBOutlet UIView *container;
25 | @property IBInspectable BOOL focusSearchBarWhenAppear;
26 |
27 | /**
28 | 键盘消隐时会自动设置 constant 为键盘在 container 视图中的高度
29 | */
30 | @property (nonatomic, weak) IBOutlet NSLayoutConstraint *keyboardAdjustLayoutConstraint;
31 |
32 | /**
33 | 供取消按钮绑定
34 |
35 | 默认会做以下事情:
36 |
37 | - 清空已键入的文本,收起键盘;
38 | - 如果 MBSearchTextField 正在搜索,且 APIName 已设置,会尝试结束对应的请求并标记搜索停止;
39 | - 调用时如果有键盘焦点且已输入文本,则不会自动退出页面,否则会导航退出页面。
40 |
41 | */
42 | - (IBAction)onCancel:(id)sender;
43 |
44 | @end
45 |
46 |
47 |
--------------------------------------------------------------------------------
/App/General/Search/SearchHighlight.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 搜索高亮支持
3 | */
4 |
5 | import B9AssociatedObject
6 |
7 | /**
8 | 添加到 model 上支持搜索高亮
9 |
10 | 基于 NSObject 的对象有自动生成,只需要声明协议即可
11 | */
12 | protocol EntitySearchHighlight: AnyObject {
13 | /// 当前的搜索关键字
14 | var searchingKeyword: String? { get set }
15 | }
16 |
17 | private let association = AssociatedObject()
18 | extension EntitySearchHighlight where Self: NSObject {
19 | var searchingKeyword: String? {
20 | get { association[self] }
21 | set { association[self] = newValue }
22 | }
23 | }
24 |
25 | extension UILabel {
26 | /// 设置文本,高亮第一个搜索词
27 | func setText(_ text: String?, searchingKeyword: String?) {
28 | guard let aText = text,
29 | let keyword = searchingKeyword?.trimmed(),
30 | let range = aText.range(of: keyword, options: .caseInsensitive, range: nil, locale: nil) else {
31 | self.text = text
32 | return
33 | }
34 | let atts = NSMutableAttributedString(string: aText)
35 | let nsRange = NSRange(range, in: aText)
36 | let highlightColor = window?.tintColor ?? UIColor(named: "primary")!
37 | atts.setAttributes([.foregroundColor: highlightColor], range: nsRange)
38 | attributedText = atts
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/App/General/Tab/MBPageScrollView.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBPageScrollView
3 |
4 | Copyright © 2018 RFUI.
5 | Copyright © 2014-2016 Beijing ZhiYun ZhiYuan Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import
12 | #import
13 |
14 | // @MBDependency:1
15 | /**
16 | 分页支持的 ScrollView
17 |
18 | */
19 | @interface MBPageScrollView : UIScrollView <
20 | RFInitializing
21 | >
22 |
23 | @property(nonatomic) NSInteger page;
24 | - (void)setPage:(NSInteger)page animated:(BOOL)animated;
25 |
26 | @property (readonly, nonatomic) NSInteger totalPage;
27 |
28 | @end
29 |
30 |
31 | @interface UIScrollView (MBPageScrolling)
32 |
33 | - (NSInteger)MBPage;
34 |
35 | - (void)MBSetPage:(NSInteger)page animated:(BOOL)animated;
36 |
37 | @end
38 |
--------------------------------------------------------------------------------
/App/General/Tab/MBTabScrollView.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBTabScrollView
3 |
4 | Copyright © 2018 RFUI.
5 | Copyright © 2014 Beijing ZhiYun ZhiYuan Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import "MBPageScrollView.h"
12 |
13 | // @MBDependency:1
14 | /**
15 | 修改了一些默认属性,其它跟 MBPageScrollView 一样
16 | */
17 | @interface MBTabScrollView : MBPageScrollView
18 |
19 | @end
20 |
--------------------------------------------------------------------------------
/App/General/Tab/MBTabScrollView.m:
--------------------------------------------------------------------------------
1 |
2 | #import "MBTabScrollView.h"
3 |
4 | @interface MBTabScrollView ()
5 | @end
6 |
7 | @implementation MBTabScrollView
8 |
9 | - (void)onInit {
10 | [super onInit];
11 | self.showsHorizontalScrollIndicator = NO;
12 | self.showsVerticalScrollIndicator = NO;
13 | self.scrollsToTop = NO;
14 | }
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/App/General/Text/MBBadgeLabel.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBBadgeLabel
3 |
4 | Copyright © 2018, 2021 BB9z.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 | #import
11 | #import
12 |
13 | // @MBDependency:2
14 | /**
15 | 红点 label
16 |
17 | 自动加圆角,调整大小和文字边距
18 | */
19 | @interface MBBadgeLabel : UILabel
20 |
21 | /**
22 | 文字边距
23 |
24 | 默认 { 2, 4, 2, 4 }
25 | */
26 | @property UIEdgeInsets contentInset;
27 | @property IBInspectable CGRect _contentInset;
28 |
29 | /**
30 | 大于 0 时,超出数量显示 maxCount+
31 | */
32 | @property IBInspectable NSInteger maxCount;
33 |
34 | /**
35 | 设置显示数量
36 |
37 | 为 0 隐藏
38 | */
39 | - (void)updateCount:(NSInteger)count;
40 |
41 | @end
42 |
--------------------------------------------------------------------------------
/App/General/Text/MBTextCountLabel.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBTextCountLabel
3 |
4 | Copyright © 2018 RFUI.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 |
11 | #import
12 | #import
13 |
14 | // @MBDependency:2
15 | /**
16 | 显示关联 textView 的文字长度,若超出指定长度,label 将变为高亮状态
17 |
18 | 默认的显示是:
19 | - 若 textView 未设置,文字置空;
20 | - 若 maxLength 未设置/为 0,只显示当前字数,不显示超出状态;
21 | - 正常显示「当前字数/最大字数」,超出 maxLength 置为高亮状态。
22 | */
23 | @interface MBTextCountLabel : UILabel <
24 | RFInitializing
25 | >
26 |
27 | /**
28 | 关联的 text view,文本修改时 count label 显示随之更新
29 | */
30 | @property (weak, nullable, nonatomic) IBOutlet UITextView *textView;
31 |
32 | /**
33 | 最大字符长度,超出 count label 外观改变以达到提示目的
34 |
35 | 不会限制 textView 实际输入
36 | */
37 | @property (nonatomic) IBInspectable NSUInteger maxLength;
38 |
39 | /**
40 | 定制最大字数部分的颜色,默认空
41 | */
42 | @property (nullable) IBInspectable UIColor *maxLengthColor;
43 |
44 | /**
45 | 如果用代码设置 textView 的文本,label 状态不会更新,可用该方法强制更新
46 | */
47 | - (void)updateUIForTextChanged;
48 |
49 | /**
50 | 自定义 textView 文本变化时如何更新 count label 样式
51 | */
52 | @property (nullable) void (^textChangeUpdateBlock)(__kindof MBTextCountLabel *__nonnull label);
53 |
54 | @end
55 |
--------------------------------------------------------------------------------
/App/General/UIKit+IBInspectable/UIButton+FontShrink.swift:
--------------------------------------------------------------------------------
1 | /*
2 | UIButton+FontShrink.swift
3 |
4 | Copyright © 2021 BB9z.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 |
11 | private extension UIButton {
12 |
13 | /**
14 | 文字显示不下时自动减少字体大小
15 | */
16 | @IBInspectable var minimumFontScale: CGFloat {
17 | get {
18 | titleLabel?.minimumScaleFactor ?? 0
19 | }
20 | set {
21 | guard let label = titleLabel else {
22 | assert(false)
23 | return
24 | }
25 | label.minimumScaleFactor = newValue
26 | label.adjustsFontSizeToFitWidth = true
27 | label.allowsDefaultTighteningForTruncation = true
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/App/General/UIKit+IBInspectable/UICollectionView+IBInspectable.h:
--------------------------------------------------------------------------------
1 | /*
2 | UICollectionView+IBInspectable
3 |
4 | Copyright © 2018, 2020 RFUI.
5 | Copyright © 2015 Beijing ZhiYun ZhiYuan Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | #import "UIKit+IBInspectable.h"
13 |
14 | /**
15 | Interface Builder 中没提供这两项的开关
16 | */
17 | @interface UICollectionView (IBInspectable)
18 | @property (nonatomic) IBInspectable BOOL allowsSelection;
19 | @property (nonatomic) IBInspectable BOOL allowsMultipleSelection;
20 | @end
21 |
--------------------------------------------------------------------------------
/App/General/UIKit+IBInspectable/UIKit+DynamicType.h:
--------------------------------------------------------------------------------
1 | /*
2 | UIKit+DynamicType
3 |
4 | Copyright © 2020 RFUI.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 |
11 | #import "UIKit+IBInspectable.h"
12 |
13 | /**
14 | Interface Builder 目前只有设置成 text styles 的样式才能自动调节大小,
15 | 设置自定义字体无 dynamic type 效果。
16 |
17 | 这里的扩展便于使用自定义字体时也能低成本支持 dynamic type。
18 | */
19 |
20 | @interface UILabel (IBDynamicType)
21 | @property IBInspectable BOOL dynamicTypeEnabled;
22 | @end
23 |
24 | @interface UIButton (IBDynamicType)
25 | @property IBInspectable BOOL dynamicTypeEnabled;
26 | @end
27 |
--------------------------------------------------------------------------------
/App/General/UIKit+IBInspectable/UIKit+DynamicType.m:
--------------------------------------------------------------------------------
1 |
2 | #import
3 |
4 | @interface UILabel (IBDynamicType)
5 | @property IBInspectable BOOL dynamicTypeEnabled;
6 | @end
7 |
8 | @implementation UILabel (DynamicType)
9 |
10 | - (BOOL)dynamicTypeEnabled {
11 | return self.adjustsFontForContentSizeCategory;
12 | }
13 | - (void)setDynamicTypeEnabled:(BOOL)dynamicTypeEnabled {
14 | self.font = [UIFontMetrics.defaultMetrics scaledFontForFont:self.font];
15 | self.adjustsFontForContentSizeCategory = YES;
16 | }
17 |
18 | @end
19 |
20 |
21 | @interface UIButton (IBDynamicType)
22 | @property IBInspectable BOOL dynamicTypeEnabled;
23 | @end
24 |
25 | @implementation UIButton (DynamicType)
26 |
27 | - (BOOL)dynamicTypeEnabled {
28 | return self.titleLabel.dynamicTypeEnabled;
29 | }
30 | - (void)setDynamicTypeEnabled:(BOOL)dynamicTypeEnabled {
31 | self.titleLabel.dynamicTypeEnabled = dynamicTypeEnabled;
32 | }
33 |
34 | @end
35 |
--------------------------------------------------------------------------------
/App/General/UIKit+IBInspectable/UIKit+IBInspectable.h:
--------------------------------------------------------------------------------
1 | /*
2 | UIKit+IBInspectable.h
3 |
4 | Copyright © 2018 RFUI.
5 | Copyright © 2015 Beijing ZhiYun ZhiYuan Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | #import
13 |
14 | #pragma message "这个文件仅用于向 Interface Builder 中增加属性的便捷访问,不应该被其他文件引用"
15 |
--------------------------------------------------------------------------------
/App/General/UIKit+IBInspectable/UILabel+ParagraphStyle.swift:
--------------------------------------------------------------------------------
1 | /*
2 | UILabel+ParagraphStyle.swift
3 |
4 | Copyright © 2020 BB9z.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 |
11 | private extension UILabel {
12 | /**
13 | 便于调整纯文本 label 的行高
14 | */
15 | @IBInspectable var lineHeightMultiple: CGFloat {
16 | get {
17 | let style = attributedText?.attributes(at: 0, effectiveRange: nil)[.paragraphStyle] as? NSParagraphStyle
18 | return style?.lineHeightMultiple ?? 0
19 | }
20 | set {
21 | guard let aStr = text else {
22 | NSLog("⚠️ text is nil, set lineHeightMultiple has no effect.")
23 | return
24 | }
25 | let style = NSMutableParagraphStyle()
26 | style.lineHeightMultiple = newValue
27 | attributedText = NSAttributedString(string: aStr, attributes: [NSAttributedString.Key.paragraphStyle: style])
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/App/General/UIKit+IBInspectable/UIScrollView+IBInspectable.h:
--------------------------------------------------------------------------------
1 | /*
2 | UIScrollView+IBInspectable
3 |
4 | Copyright © 2018, 2020 RFUI.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 |
11 | #import "UIKit+IBInspectable.h"
12 |
13 | /**
14 | Interface Builder 中没提供这项的开关
15 | */
16 | @interface UIScrollView (IBInspectable)
17 | @property (nonatomic) IBInspectable BOOL scrollsToTop;
18 | @end
19 |
--------------------------------------------------------------------------------
/App/General/UIKit+IBInspectable/UITextView+ContainerInset.h:
--------------------------------------------------------------------------------
1 | /*
2 | UITextView+ContainerInset
3 |
4 | Copyright © 2018 RFUI.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 |
11 | #import "UIKit+IBInspectable.h"
12 |
13 | @interface UITextView (ContainerInset)
14 | /// 修改 textContainerInset
15 | @property IBInspectable CGRect RFTextContainerInset;
16 | @end
17 |
--------------------------------------------------------------------------------
/App/General/UIKit+IBInspectable/UITextView+ContainerInset.m:
--------------------------------------------------------------------------------
1 |
2 | #import
3 |
4 | @interface UITextView (ContainerInset)
5 | @property IBInspectable CGRect RFTextContainerInset;
6 | @end
7 |
8 | @implementation UITextView (ContainerInset)
9 | @dynamic RFTextContainerInset;
10 |
11 | - (void)setRFTextContainerInset:(CGRect)inset {
12 | self.textContainerInset = (UIEdgeInsets){ inset.origin.x, inset.origin.y, inset.size.width, inset.size.height };
13 | }
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/App/Launching/ApplicationCondition.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ApplicationCondition.swift
3 | // App
4 | //
5 |
6 | #if canImport(B9Condition)
7 | import B9Condition
8 |
9 | // swiftlint:disable:next identifier_name
10 | func AppCondition() -> Condition> {
11 | globalCondition
12 | }
13 | private let globalCondition = Condition>()
14 |
15 | /**
16 | 关于状态
17 |
18 | ApplicationCondition 描述的应该是可以持续的状态,而不是一个瞬间发生的事件,
19 | 事件用通知、delegate 之类的响应就好了,Condition 不是干这个的。
20 |
21 | @warning ApplicationCondition 状态不应持久化
22 | */
23 | enum ApplicationCondition {
24 | // - 应用整体状态
25 | /// 应用现在处于前台
26 | case appInForeground
27 |
28 | /// 应用启动后至少进过一次前台
29 | case appHasEnterForegroundOnce
30 |
31 | /// 网络是否在线
32 | case online
33 |
34 | /// 使用 Wi-Fi 联网
35 | case wifi
36 |
37 | // - 用户状态
38 | /// 用户已登入
39 | case userHasLogged
40 |
41 | /// 本次启动当前用户的用户信息已成功获取过
42 | case userInfoFetched
43 |
44 | // - 模块生命周期
45 | /// 导航已加载
46 | case navigationLoaded
47 |
48 | /// 主页已载入
49 | case homeLoaded
50 | }
51 |
52 | #endif
53 |
--------------------------------------------------------------------------------
/App/Model/JSON/CommentEntity.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommentEntity.swift
3 | // App
4 | //
5 |
6 | /**
7 | 帖子评论
8 |
9 | https://bb9z.github.io/API-Documentation-Sample/Sample/Entity#CommentEntity
10 | */
11 | @objc(CommentEntity)
12 | @objcMembers
13 | class CommentEntity: MBModel,
14 | IdentifierEquatable {
15 |
16 | var uid: String = ""
17 | var from: UserEntity?
18 | var to: UserEntity?
19 | var createTime: Date?
20 | var content: String?
21 | var replies: [CommentEntity]?
22 |
23 | // MARK: -
24 |
25 | override func isEqual(_ object: Any?) -> Bool {
26 | isUIDEqual(object)
27 | }
28 | override var hash: Int { uid.hashValue }
29 |
30 | override class func classForCollectionProperty(propertyName: String!) -> AnyClass! {
31 | if propertyName == #keyPath(CommentEntity.replies) {
32 | return CommentEntity.self
33 | }
34 | return super.classForCollectionProperty(propertyName: propertyName)
35 | }
36 |
37 | override class func keyMapper() -> JSONKeyMapper! {
38 | JSONKeyMapper.baseMapper(JSONKeyMapper.forSnakeCase(), withModelToJSONExceptions: [
39 | "uid": "id",
40 | ])
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/App/Model/JSON/README.md:
--------------------------------------------------------------------------------
1 | # JSON 目录
2 |
3 | 存放 JSON 数据模型。
4 |
5 | 推荐做法:
6 |
7 | * 类名用 Entity 结尾;
8 | * 请求发送、接收时有时需要建立只在该场景下的中转 model,此时类名用 RequestEntity 或 ResponseEntity 结尾,此类 model 只是用做封装,不应在其中包含业务逻辑;
9 | * 提倡富 model,在 model 上承担更多的业务逻辑(如请求发送、发送状态变化通知、状态判断、结构转换等任务),而不是简单的只是声明几个属性;
10 | * 跟业务无关的、与特定页面关联紧密的 model 推荐和 view、vc 放在一起。
11 |
--------------------------------------------------------------------------------
/App/Model/JSON/UserEntity.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserEntity.swift
3 | // App
4 | //
5 |
6 | /**
7 | 用户模型
8 |
9 | https://bb9z.github.io/API-Documentation-Sample/Sample/Entity#UserEntity
10 | */
11 | @objc(UserEntity)
12 | @objcMembers
13 | class UserEntity: MBModel,
14 | IdentifierEquatable {
15 |
16 | var uid: String = ""
17 | var name: String = ""
18 | var introduction: String?
19 | var avatar: String?
20 | var topicCount: Int = 0
21 | var likedCount: Int = 0
22 |
23 | // MARK: -
24 |
25 | override func isEqual(_ object: Any?) -> Bool {
26 | isUIDEqual(object)
27 | }
28 | override var hash: Int { uid.hashValue }
29 |
30 | override class func keyMapper() -> JSONKeyMapper! {
31 | JSONKeyMapper.baseMapper(JSONKeyMapper.forSnakeCase(), withModelToJSONExceptions: [
32 | "uid": "id",
33 | ])
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/App/Model/README.md:
--------------------------------------------------------------------------------
1 | # Model 目录
2 |
3 | 存放数据模型和其他数据相关的类。
4 |
5 | 数据库 model 名以 Model 结尾;JSON model 名以 Entity 结尾。
6 |
--------------------------------------------------------------------------------
/App/Model/Support/FileURL.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileURL.swift
3 | // App
4 | //
5 |
6 | /**
7 | 文件路径集中管理
8 | */
9 | enum FileURL {
10 |
11 | /// 数据库路径
12 | static func database() throws -> URL {
13 | try FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("App.db")
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/App/Model/Support/JSONValueTransformer+App.m:
--------------------------------------------------------------------------------
1 | /*
2 | JSONValueTransformer (App)
3 |
4 | Copyright © 2018, 2020 RFUI.
5 | Copyright © 2014-2016 Beijing ZhiYun ZhiYuan Information Technology Co., Ltd.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import "MBModel.h"
12 |
13 | /**
14 | JSON Mode 的 ValueTransformer,用于将 JSON 类型转为其他类型
15 |
16 | 通常用于时间转换,请根据项目格式进行调整
17 | */
18 | @interface JSONValueTransformer (App)
19 | @end
20 |
21 | @implementation JSONValueTransformer (App)
22 |
23 | /* 时间是浮点时间戳的实现
24 |
25 | #pragma clang diagnostic push
26 | #pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
27 | - (NSDate *)NSDateFromNSNumber:(NSNumber *)string {
28 | NSTimeInterval time = [string floatValue];
29 | return [NSDate dateWithTimeIntervalSince1970:time];
30 | }
31 | #pragma clang diagnostic pop
32 |
33 | - (NSDate *)NSDateFromNSString:(NSString*)string {
34 | NSTimeInterval time = [string floatValue];
35 | return [NSDate dateWithTimeIntervalSince1970:time];
36 | }
37 |
38 | - (NSString *)JSONObjectFromNSDate:(NSDate *)date {
39 | return [NSString stringWithFormat:@"%ld", (long)[date timeIntervalSince1970]];
40 | }
41 | */
42 |
43 | @end
44 |
--------------------------------------------------------------------------------
/App/Model/Support/MBErrorCode.m:
--------------------------------------------------------------------------------
1 |
2 | #import "MBErrorCode.h"
3 |
--------------------------------------------------------------------------------
/App/Model/Types.swift:
--------------------------------------------------------------------------------
1 | /*
2 | 对特殊约定的数据类型进行定义以示区分
3 | */
4 |
5 | /// 账号系统 ID 的类型
6 | typealias AccountID = String
7 |
8 | typealias MBIdentifier = String
9 |
10 | /// 专用于标示日期哪一天
11 | typealias MBDateDayIdentifier = String
12 |
13 | /// 整形 ID
14 | typealias MBID = Int64
15 |
16 | /* 🔰 例如
17 |
18 | /// 服务器时长用的是整型
19 | typealias Duration = Int32
20 |
21 | /// 从 1 开始的序号
22 | typealias NIdx = Int
23 | */
24 |
25 | protocol ListDisplaying {
26 | associatedtype ListType
27 |
28 | var listView: ListType { get }
29 | }
30 |
--------------------------------------------------------------------------------
/App/Scene/Home/HomeVC.swift:
--------------------------------------------------------------------------------
1 | import B9Condition
2 |
3 | /**
4 | 首页
5 | */
6 | class HomeViewController: UIViewController, StoryboardCreation {
7 | static var storyboardID: StoryboardID { .main }
8 |
9 | @IBAction private func navigationPop(_ sender: Any) {
10 | Current.hud.showInfoStatus("主页的该按钮用于调整导航返回按钮图片位置,请删除")
11 | }
12 |
13 | override func viewDidAppear(_ animated: Bool) {
14 | super.viewDidAppear(animated)
15 | // 通知主页已加载完毕,启动闪屏会等待这个信号,见 RootViewController
16 | AppCondition().set(on: [.homeLoaded])
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/App/Scene/Topic/TopicListVCs.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TopicListVCs.swift
3 | // App
4 | //
5 |
6 | /**
7 | 推荐帖子列表
8 | */
9 | class TopicRecommendListController: MBTableListController, StoryboardCreation {
10 | static var storyboardID: StoryboardID { .topic }
11 | }
12 |
13 | import Debugger
14 | extension TopicRecommendListController: DebugActionSource {
15 | func debugActionItems() -> [DebugActionItem] {
16 | [DebugActionItem("zz", action: nil)]
17 | }
18 | }
19 |
20 | #if PREVIEW
21 | import SwiftUI
22 | struct TopicRecommendListPreview: PreviewProvider {
23 | static var previews: some View {
24 | ViewControllerPreview {
25 | TopicRecommendListController.newFromStoryboard()
26 | }
27 | }
28 | }
29 | #endif
30 |
--------------------------------------------------------------------------------
/App/Scene/User/UserAvatarView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserAvatarView.swift
3 | // App
4 | //
5 |
6 | /**
7 | 用户头像 view
8 | */
9 | class UserAvatarView: MBImageView {
10 | @objc var item: UserEntity? {
11 | didSet {
12 | imageURL = item?.avatar
13 | }
14 | }
15 |
16 | override func onInit() {
17 | super.onInit()
18 | // 头像应低优先加载,为其他内容让路
19 | imageLoadInLowPriority = true
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/App/Service/MBDebug/Components/MBFlexInterface.h:
--------------------------------------------------------------------------------
1 |
2 | #import
3 |
4 | /**
5 | 当应用包含 FLEX 时 https://github.com/FLEXTool/FLEX ,以下方法可用
6 | */
7 | @interface MBFlexInterface : NSObject
8 |
9 | /// 显示 FLEX 浮窗
10 | + (void)showFlexExplorer;
11 |
12 | /// 创建查看 object 的 vc
13 | + (nullable UIViewController *)explorerViewControllerForObject:(nullable id)object;
14 |
15 | /// 创建用于浏览数据库文件的 vc,注意只有特定文件后缀才支持,详见 FLEXTableListViewController 的实现
16 | + (nullable UIViewController *)databaseViewControllerWithPath:(nullable NSString *)path;
17 |
18 | @end
19 |
--------------------------------------------------------------------------------
/App/Service/MBDebug/Components/MBFlexInterface.m:
--------------------------------------------------------------------------------
1 |
2 | #import "MBFlexInterface.h"
3 |
4 | @implementation MBFlexInterface
5 |
6 | #pragma clang diagnostic push
7 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
8 |
9 | + (void)showFlexExplorer {
10 | Class flex = NSClassFromString(@"FLEXManager");
11 | if (!flex) {
12 | NSLog(@"FLEX 未加载");
13 | return;
14 | }
15 | SEL selManager = NSSelectorFromString(@"sharedManager");
16 | SEL selShow = NSSelectorFromString(@"showExplorer");
17 | [[flex performSelector:selManager] performSelector:selShow];
18 | }
19 |
20 | + (UIViewController *)explorerViewControllerForObject:(id)object {
21 | Class clsFactory = NSClassFromString(@"FLEXObjectExplorerFactory");
22 | if (!clsFactory) return nil;
23 | SEL sel = NSSelectorFromString(@"explorerViewControllerForObject:");
24 | return [clsFactory performSelector:sel withObject:object];
25 | }
26 |
27 | + (UIViewController *)databaseViewControllerWithPath:(NSString *)path {
28 | Class clsController = NSClassFromString(@"FLEXTableListViewController");
29 | if (!clsController) return nil;
30 | SEL sel = NSSelectorFromString(@"initWithPath:");
31 | return [[clsController alloc] performSelector:sel withObject:path];
32 | }
33 |
34 | #pragma clang diagnostic pop
35 |
36 | @end
37 |
--------------------------------------------------------------------------------
/App/Service/MBDebug/Components/MBNavigationController+MBDebugReleaseChecking.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBNavigationController ReleaseChecking
3 | MBDebug
4 |
5 | Copyright © 2018 RFUI.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import "MBNavigationController.h"
12 |
13 | #if RFDEBUG
14 |
15 | @interface MBNavigationController (MBDebugReleaseChecking)
16 | @end
17 |
18 | #endif
19 |
20 | @protocol MBDebugNavigationReleaseChecking
21 | @optional
22 | - (BOOL)debugShouldIgnoralCheckReleaseForViewController:(UIViewController *)viewController;
23 | @end
24 |
25 |
--------------------------------------------------------------------------------
/App/Service/MBFileUploader/API+FileUpload.h:
--------------------------------------------------------------------------------
1 | /*
2 | API+FileUpload
3 |
4 | Copyright © 2018, 2020-2021 RFUI.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 |
11 | #import "Common.h"
12 |
13 | @protocol MBFileUploadTask
14 | /// 取消请求
15 | - (void)cancel;
16 | @end
17 |
18 | /**
19 | 提供文件上传接口
20 | */
21 | @interface API (MBFileUpload)
22 |
23 | /**
24 | 上传 JPEG 图像数据
25 |
26 | @param callback item 是上传好的 URL 地址
27 | */
28 | - (nullable id)uploadImageWithData:(nonnull NSData *)jpegData callback:(nonnull MBGeneralCallback)callback;
29 |
30 | /**
31 | 文件上传
32 |
33 | @param callback item 是上传好的 URL 地址
34 | */
35 | - (nullable id)uploadFile:(nonnull NSURL *)fileURL callback:(nonnull MBGeneralCallback)callback;
36 |
37 | @end
38 |
39 |
--------------------------------------------------------------------------------
/App/Service/MBFileUploader/OSS/OSSConfigEntity.h:
--------------------------------------------------------------------------------
1 | /*
2 | OSSConfigEntity
3 | MBOSSUploader
4 |
5 | Copyright © 2018 RFUI.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | #import "MBModel.h"
12 |
13 | /**
14 | 图片上传到 OSS 时需要的上传凭证
15 |
16 | STS 鉴权 https://help.aliyun.com/document_detail/32059.html
17 |
18 | 根据具体项目按需修改键值
19 | */
20 | @interface OSSConfigEntity : MBModel
21 |
22 | @property NSString *bucket;
23 |
24 | /// 形如 https://example.oss-cn-beijing.aliyuncs.com
25 | @property NSString *host;
26 |
27 | /// 形如 https://oss-cn-beijing.aliyuncs.com
28 | @property (nonatomic) NSString *endpoint;
29 |
30 | @property NSString *accessKeyId;
31 | @property NSString *accessKeySecret;
32 | @property NSString *securityToken;
33 | // 未使用
34 | @property NSString *expiration;
35 |
36 | - (NSURL *)destinationURLWithObjectKey:(NSString *)objectKey;
37 |
38 | @end
39 |
--------------------------------------------------------------------------------
/App/Service/MBFileUploader/OSS/OSSConfigEntity.m:
--------------------------------------------------------------------------------
1 |
2 | #import "OSSConfigEntity.h"
3 | #import
4 |
5 | @implementation OSSConfigEntity
6 |
7 | - (NSString *)endpoint {
8 | if (_endpoint) return _endpoint;
9 | NSArray *part = [self.host componentsSeparatedByString:@"."];
10 | // 取后三
11 | _endpoint = [NSString.alloc initWithFormat:@"https://%@", [[part rf_subarrayWithRangeLocation:-1 length:-3] componentsJoinedByString:@"."]];
12 | return _endpoint;
13 | }
14 |
15 | - (NSURL *)destinationURLWithObjectKey:(NSString *)objectKey {
16 | NSString *url = [NSString stringWithFormat:@"%@/%@", self.host, objectKey];
17 | return [NSURL URLWithString:url];
18 | }
19 |
20 | @end
21 |
--------------------------------------------------------------------------------
/App/Service/MBImageRenderer/MBImageRenderer.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBImageRenderer
3 |
4 | Copyright © 2018 RFUI.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 |
11 | #import
12 |
13 | // @MBDependency:2
14 | /**
15 | 将 View Controller 的内容渲染为自适应尺寸的图片
16 |
17 | - 多用于复杂分享图片的生成
18 | - 在 Interface Builder 中进行可视化设计,并利用 Auto Layout 简化内容的自适应
19 |
20 | */
21 | @interface MBImageRenderer : NSObject
22 |
23 | - (null_unspecified instancetype)init NS_UNAVAILABLE;
24 |
25 | /**
26 | @param viewController Must not be nil
27 | */
28 | - (nonnull instancetype)initWithViewController:(nonnull UIViewController *)viewController NS_DESIGNATED_INITIALIZER;
29 |
30 | @property (readonly, nonnull) UIViewController *viewController;
31 |
32 | /// 截图的 scale,默认为 2
33 | @property CGFloat renderScale;
34 |
35 | /**
36 | 准备渲染,并更新布局
37 |
38 | 布局分两种,如果 viewController 指定了非零的 preferredContentSize,则固定为该尺寸;
39 | 否则需要用 Auto Layout 撑起 view 的尺寸(宽高都需要定义)
40 |
41 | 这个方法可以反复调用。内部会把 view controller 的 view 安装到 keywindow 里
42 | */
43 | - (void)prepareForRendering;
44 |
45 | /**
46 | 渲染出图,然后清理
47 |
48 | 默认会先调用一次 prepareForRendering
49 | */
50 | - (nullable UIImage *)renderAndClean;
51 |
52 | @end
53 |
--------------------------------------------------------------------------------
/App/Service/README.md:
--------------------------------------------------------------------------------
1 | # Service 目录
2 |
3 | 功能模块。
4 |
5 | 有些目录组件默认未导入(有多种实现,或不常用),需要手动导入,此时在 Xcode 里可能是一个空的文件夹。
6 |
--------------------------------------------------------------------------------
/App/Supporting Files/Common.h:
--------------------------------------------------------------------------------
1 | /*
2 | Common.h
3 |
4 | 供 Objecive-C 代码引入 Swift 代码
5 | */
6 |
7 | #pragma once
8 |
9 | #import
10 | #import "App-Swift.h"
11 |
--------------------------------------------------------------------------------
/App/Supporting Files/PrivacyInfo.xcprivacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPrivacyAccessedAPITypes
6 |
7 |
8 | NSPrivacyAccessedAPIType
9 | NSPrivacyAccessedAPICategoryUserDefaults
10 | NSPrivacyAccessedAPITypeReasons
11 |
12 | CA92.1
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/App/Supporting Files/alpha.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.network.client
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/App/Supporting Files/debug.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.network.client
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/App/Supporting Files/release.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.network.client
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Frameworks/MBAppKit/MBAppKit/Category/NSObject+MBAppKit.h:
--------------------------------------------------------------------------------
1 | /*!
2 | NSObject+MBAppKit
3 | MBAppKit
4 |
5 | Copyright © 2018 RFUI.
6 | Copyright © 2016 Beijing ZhiYun ZhiYuan Technology Co., Ltd.
7 | https://github.com/RFUI/MBAppKit
8 |
9 | Apache License, Version 2.0
10 | http://www.apache.org/licenses/LICENSE-2.0
11 | */
12 | #import
13 |
14 | @interface NSObject (MBAppKit)
15 |
16 | /// 类名字符串,Swift 类名只保留 . 最后的部分
17 | @property (class, nonnull, readonly) NSString *className;
18 |
19 | /// 类名字符串,Swift 类名只保留 . 最后的部分
20 | @property (nonnull, readonly) NSString *className;
21 |
22 | @end
23 |
24 | /**
25 | 比较两个对象,两个对象都是 nil 认为是相同的,要用 isEqual: 还得注意判空
26 | */
27 | BOOL NSObjectIsEquail(id __nullable a, id __nullable b);
28 |
--------------------------------------------------------------------------------
/Frameworks/MBAppKit/MBAppKit/Category/NSObject+MBAppKit.m:
--------------------------------------------------------------------------------
1 |
2 | #import "NSObject+MBAppKit.h"
3 |
4 | @implementation NSObject (MBAppKit)
5 |
6 | + (NSString *)className {
7 | NSString *className = NSStringFromClass(self);
8 | if ([className rangeOfString:@"."].location != NSNotFound) {
9 | // Swift class name contains module name
10 | return [className componentsSeparatedByString:@"."].lastObject;
11 | }
12 | return className;
13 | }
14 |
15 | - (NSString *)className {
16 | return self.class.className;
17 | }
18 |
19 | @end
20 |
21 | BOOL NSObjectIsEquail(id __nullable a, id __nullable b) {
22 | if (!a && !b) {
23 | // 都空
24 | return YES;
25 | }
26 | if (!a || !b) {
27 | // 只有一个是空
28 | return NO;
29 | }
30 | return [a isEqual:b];
31 | }
32 |
--------------------------------------------------------------------------------
/Frameworks/MBAppKit/MBAppKit/MBGeneral.swift:
--------------------------------------------------------------------------------
1 | /*!
2 | MBGeneral
3 | MBAppKit
4 |
5 | Copyright © 2018, 2023 BB9z.
6 | Copyright © 2016 Beijing ZhiYun ZhiYuan Information Technology Co., Ltd.
7 | https://github.com/RFUI/MBAppKit
8 |
9 | Apache License, Version 2.0
10 | http://www.apache.org/licenses/LICENSE-2.0
11 | */
12 |
13 | import Foundation
14 |
15 | /// 统一的列表界面
16 | protocol GeneralListDisplaying {
17 | associatedtype ListType
18 |
19 | var listView: ListType! { get }
20 |
21 | func refresh()
22 | }
23 |
--------------------------------------------------------------------------------
/Frameworks/MBAppKit/MBAppKit/MBGeneral/MBGeneralCallback.m:
--------------------------------------------------------------------------------
1 |
2 | #import "MBGeneralCallback.h"
3 |
4 | MBGeneralCallback _Nonnull MBSafeCallback(MBGeneralCallback _Nullable callback) {
5 | return ^(BOOL success, id _Nullable item, NSError *_Nullable error) {
6 | if (!callback) return;
7 | dispatch_sync_on_main(^{
8 | if (callback) {
9 | callback(success, item, error);
10 | }
11 | });
12 | };
13 | }
14 |
15 | MBGeneralCallback _Nonnull MBSafeCallbackExecutedOnDispatchQueue(MBGeneralCallback _Nullable callback, dispatch_queue_t _Nonnull queue) {
16 | return ^(BOOL success, id _Nullable item, NSError *_Nullable error) {
17 | if (!callback) return;
18 | dispatch_async(queue, ^{
19 | if (callback) {
20 | callback(success, item, error);
21 | }
22 | });
23 | };
24 | }
25 |
--------------------------------------------------------------------------------
/Frameworks/MBAppKit/MBAppKit/MBGeneral/MBGeneralCellResponding.m:
--------------------------------------------------------------------------------
1 |
2 | #import "MBGeneralCellResponding.h"
3 |
4 | #if !TARGET_OS_OSX
5 | BOOL MBGeneralCellRespondingTableViewDidSelectImplementation(UITableView *__nonnull tableView, NSIndexPath *__nonnull indexPath) {
6 | UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
7 | if (![cell respondsToSelector:@selector(respondsCellSelection)]) {
8 | return NO;
9 | }
10 | if (cell.respondsCellSelection) {
11 | [cell onCellSelected];
12 | return YES;
13 | }
14 | return NO;
15 | }
16 |
17 | BOOL MBGeneralCellRespondingCollectionViewDidSelectImplementation(UICollectionView *__nonnull collectionView, NSIndexPath *__nonnull indexPath) {
18 | UICollectionViewCell *cell = (__kindof UICollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
19 | if (![cell respondsToSelector:@selector(respondsCellSelection)]) {
20 | return NO;
21 | }
22 | if (cell.respondsCellSelection) {
23 | [cell onCellSelected];
24 | return YES;
25 | }
26 | return NO;
27 | }
28 | #endif
29 |
--------------------------------------------------------------------------------
/Frameworks/MBAppKit/MBAppKit/MBGeneral/MBGeneralItemExchanging.h:
--------------------------------------------------------------------------------
1 | /*!
2 | MBEntityExchanging
3 | MBAppKit
4 |
5 | Copyright © 2018 RFUI.
6 | Copyright © 2014-2016 Beijing ZhiYun ZhiYuan Information Technology Co., Ltd.
7 | Copyright © 2014 Chinamobo Co., Ltd.
8 | https://github.com/RFUI/MBAppKit
9 |
10 | Apache License, Version 2.0
11 | http://www.apache.org/licenses/LICENSE-2.0
12 | */
13 | #import
14 |
15 | /**
16 | 标准视图间 model 交换协议
17 | */
18 | @protocol MBGeneralItemExchanging
19 | @required
20 | @property (nonatomic, nullable, strong) id item;
21 |
22 | @optional
23 |
24 | /// 期望的 item 类型
25 | - (nonnull Class)preferredItemClass;
26 |
27 | @end
28 |
29 | /**
30 | 如果 destination 符合 MBGeneralItemExchanging 声明,就把 value 赋值给 destination 的 item 并返回 YES。否则返回 NO
31 |
32 | 便于在 Swift 中非显式声明协议传值困难
33 | */
34 | FOUNDATION_EXTERN BOOL MBGeneralItemPassValue(id __nullable destination, id __nullable value);
35 |
36 |
37 | /**
38 | item 的可选协议
39 | */
40 | @protocol MBItemExchanging
41 | @optional
42 | - (NSString *_Nullable)displayString;
43 | @end
44 |
--------------------------------------------------------------------------------
/Frameworks/MBAppKit/MBAppKit/MBGeneral/MBGeneralItemExchanging.m:
--------------------------------------------------------------------------------
1 |
2 | #import "MBGeneralItemExchanging.h"
3 |
4 | BOOL MBGeneralItemPassValue(id destination, id value) {
5 | id dst = destination;
6 | if (![dst respondsToSelector:@selector(setItem:)]) {
7 | return NO;
8 | }
9 | Class exceptClass = nil;
10 | if ([destination respondsToSelector:@selector(preferredItemClass)]) {
11 | exceptClass = dst.preferredItemClass;
12 | }
13 | if (exceptClass && value) {
14 | if (![value isKindOfClass:exceptClass]) {
15 | return NO;
16 | }
17 | }
18 | dst.item = value;
19 | return YES;
20 | }
21 |
--------------------------------------------------------------------------------
/Frameworks/MBAppKit/MBAppKit/MBGeneral/MBGeneralListDisplaying.h:
--------------------------------------------------------------------------------
1 | /*!
2 | MBGeneralListDisplaying
3 | MBAppKit
4 |
5 | Copyright © 2018 RFUI.
6 | Copyright © 2016 Beijing ZhiYun ZhiYuan Information Technology Co., Ltd.
7 | https://github.com/RFUI/MBAppKit
8 |
9 | Apache License, Version 2.0
10 | http://www.apache.org/licenses/LICENSE-2.0
11 | */
12 | #import
13 |
14 | /*!
15 | 统一的列表界面
16 | */
17 |
18 | @protocol MBGeneralListDisplaying
19 | @optional
20 |
21 | - (id)listView;
22 |
23 | - (void)refresh;
24 |
25 | @end
26 |
--------------------------------------------------------------------------------
/Frameworks/MBAppKit/MBAppKit/MBGeneral/MBGeneralSelection.h:
--------------------------------------------------------------------------------
1 | /*
2 | MBGeneralSelection
3 |
4 | Copyright © 2018 RFUI.
5 | https://github.com/BB9z/iOS-Project-Template
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 | #import
11 |
12 | /**
13 | 声明对象会响应选取事件
14 |
15 | 典型场景:组件嵌套时,里层的代码通过响应者链把选取事件发送给上层
16 | */
17 | @protocol MBGeneralSelection
18 | @optional
19 | - (void)onSelect:(nonnull id)sender;
20 |
21 | - (void)onDeselect:(nonnull id)sender;
22 |
23 | @end
24 |
--------------------------------------------------------------------------------
/Frameworks/MBAppKit/MBAppKit/MBGeneral/MBGeneralViewControllerStateTransitions.h:
--------------------------------------------------------------------------------
1 | /*!
2 | MBGeneralViewControllerStateTransitions
3 | MBAppKit
4 |
5 | Copyright © 2018 RFUI.
6 | Copyright © 2016 Beijing ZhiYun ZhiYuan Information Technology Co., Ltd.
7 | https://github.com/RFUI/MBAppKit
8 |
9 | Apache License, Version 2.0
10 | http://www.apache.org/licenses/LICENSE-2.0
11 | */
12 | #import
13 |
14 | /**
15 | View controller 生命周期约定
16 | */
17 | @protocol MBGeneralViewControllerStateTransitions
18 |
19 | @optional
20 |
21 | /**
22 | view 是否显示过,一般用于初始化逻辑
23 |
24 | 一般在 viewDidLoad 方法置 NO,在 viewDidAppear 方法置为 YES
25 | */
26 | //@property (readwrite) BOOL hasViewAppeared;
27 |
28 | /**
29 | view controller 的 viewDidAppear: 方法不会在应用从后台切回前台调用,自定义 vc 容器通知子 vc 显示更新也没有现成的方法。这个方法就是为了解决这两种情况约定的。
30 |
31 | 如何使用
32 | - 导航控制器在应用从后台切回前台时,应该在当前显示的 vc 上调用该方法
33 | - 自定义容器 vc 在切换多个子 vc 显示时调用该方法
34 |
35 | @warning 该方法目前没有配对的 disappear 方法,类似通知监听添加/移除的操作不应放在该方法中维护,一次出现可能会调用该方法多次
36 | */
37 | - (void)MBViewDidAppear:(BOOL)animated;
38 |
39 | @end
40 |
--------------------------------------------------------------------------------
/Frameworks/MBAppKit/MBAppKit/MBGeneral/MBGeneralViewControllerStateTransitions.m:
--------------------------------------------------------------------------------
1 |
2 | #import "MBGeneralViewControllerStateTransitions.h"
3 |
--------------------------------------------------------------------------------
/Frameworks/MBAppKit/MBAppKit/MBGeneral/README.md:
--------------------------------------------------------------------------------
1 | # MBGeneral
2 |
3 | 这部分可以说是整个项目的精华之一,我们约定了一套默认的行为规范,通过统一行为,我们能获得太多的好处。
4 |
5 | 举个例子,项目里绝大多数,不管是 view controller、cell 还是复用的 view 组件上都有一个 item 属性。这会带来什么呢?
6 |
7 | 1. 模块与模块间的传值方式统一了,不论什么界面,先 newFromStoryboard,再 setItem: 或 setItems: 无脑传就是了。
8 |
9 | 不光代码容易写,通过 segue 跳转页面变得极为轻松自由:不论是通过 cell 还是 button,我只需要连一个 segue,具体值怎么传的,基类里面都做好了。改个跳转?重连一下线就好了,代码什么的完全不用改。
10 |
11 | 2. 模块替换变得容易了,大家都支持这些方法,用的时候也都使用这些方法,换的时候改个类名就 OK 了。想想把一个单排流改成双排需要做什么?Storyboard 中换一下嵌入列表的类型,view controller 的基类再一换,很多时候就是改这么两个类名就完成的事。
12 |
13 | 当项目中都遵循这套统一的规范时,我们可以把重复的工作放到基类里面,从而省去很多工作。
14 |
--------------------------------------------------------------------------------
/Frameworks/README.md:
--------------------------------------------------------------------------------
1 | # Frameworks
2 |
3 | 保存手工导入的第三方依赖。
4 |
--------------------------------------------------------------------------------
/Packages/Action/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright © 2021-2022 BB9z
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Packages/Action/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "B9Action",
8 | // platforms: [.iOS(.v10), .macOS(.v10_12), .tvOS(.v10), .watchOS(.v3)],
9 | products: [
10 | // Products define the executables and libraries a package produces, and make them visible to other packages.
11 | .library(
12 | name: "B9Action",
13 | targets: ["B9Action"]),
14 | ],
15 | dependencies: [
16 | // Dependencies declare other packages that this package depends on.
17 | // .package(url: /* package url */, from: "1.0.0"),
18 | ],
19 | targets: [
20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
21 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
22 | .target(
23 | name: "B9Action",
24 | dependencies: []),
25 | .testTarget(
26 | name: "B9ActionTests",
27 | dependencies: ["B9Action"]),
28 | ]
29 | )
30 |
--------------------------------------------------------------------------------
/Packages/Action/README.md:
--------------------------------------------------------------------------------
1 | # B9Action
2 |
3 | [](https://swift.org)
4 | [](https://swift.org/package-manager)
5 | [](https://github.com/b9swift/Action/actions)
6 | [](https://gitee.com/b9swift/Action)
7 | [](https://github.com/b9swift/Action)
8 |
9 | 一个简单的基础组件,主要目的是为 target/selector 模式和 block 调用提供统一的界面。
10 |
11 | A simple foundational component, primarily intended to provide a uniform interface for target/selector patterns and block invocations.
12 |
13 | ## 集成
14 |
15 | 使用 Swift Package Manager 或手工导入。
16 |
17 | You can also use [GitHub source](https://github.com/b9swift/Action).
18 |
19 | ## Installation
20 |
21 | Using Swift Package Manager or importing manually.
22 |
23 | 你也可以使用 [gitee 镜像](https://gitee.com/b9swift/Action)。
24 |
--------------------------------------------------------------------------------
/Packages/AppFramework/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 |
--------------------------------------------------------------------------------
/Packages/AppFramework/.swiftpm/AppFramework.xctestplan:
--------------------------------------------------------------------------------
1 | {
2 | "configurations" : [
3 | {
4 | "id" : "A828EEA2-5FC5-4F4A-9E6C-FAB2E769F847",
5 | "name" : "Test Scheme Action",
6 | "options" : {
7 |
8 | }
9 | }
10 | ],
11 | "defaultOptions" : {
12 |
13 | },
14 | "testTargets" : [
15 | {
16 | "target" : {
17 | "containerPath" : "container:",
18 | "identifier" : "AppFrameworkTests",
19 | "name" : "AppFrameworkTests"
20 | }
21 | }
22 | ],
23 | "version" : 1
24 | }
25 |
--------------------------------------------------------------------------------
/Packages/AppFramework/.swiftpm/xcode/package.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Packages/AppFramework/AppFramework.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Packages/AppFramework/AppFramework.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "B9Action",
6 | "repositoryURL": "https://github.com/b9swift/Action.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "32ccacba992b921e304438ca6d6cfa20fef6e840",
10 | "version": "1.1.0"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/Packages/AppFramework/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "B9Action",
6 | "repositoryURL": "https://github.com/b9swift/Action.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "32ccacba992b921e304438ca6d6cfa20fef6e840",
10 | "version": "1.1.0"
11 | }
12 | },
13 | {
14 | "package": "B9MulticastDelegate",
15 | "repositoryURL": "https://github.com/b9swift/MulticastDelegate.git",
16 | "state": {
17 | "branch": null,
18 | "revision": "38b3a2243036ac01053ed1894e625755c20196a0",
19 | "version": "1.1.1"
20 | }
21 | }
22 | ]
23 | },
24 | "version": 1
25 | }
26 |
--------------------------------------------------------------------------------
/Packages/AppFramework/README.md:
--------------------------------------------------------------------------------
1 | # AppFramework
2 |
3 | 将应用的基础框架从应用端抽离出来,目标:
4 |
5 | 1. 便于框架升级;
6 | 2. 隐藏实现细节,保护内部状态不可再应用端随意修改;
7 | 3. 进行较完整的测试。
8 |
9 | 需要重载的类仍以 `MB` 开头,直接使用的符号则不加前缀。
10 |
--------------------------------------------------------------------------------
/Packages/AppFramework/Sources/AppFramework/Extensions/Collection+AF.swift:
--------------------------------------------------------------------------------
1 | /*!
2 | Collection+AF.swift
3 | AppFramework
4 |
5 | Copyright © 2023 BB9z.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | import Foundation
13 |
14 | public extension Sequence where Iterator.Element: Hashable {
15 | /// 返回去重的序列
16 | func uniqued() -> [Iterator.Element] {
17 | var seen = [Iterator.Element: Bool]()
18 | return filter { seen.updateValue(true, forKey: $0) == nil }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Packages/AppFramework/Sources/AppFramework/MBAssert.swift:
--------------------------------------------------------------------------------
1 | /*!
2 | MBAssert
3 | AppFramework
4 |
5 | Copyright © 2023 BB9z.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | import Foundation
13 |
14 | /// 框架提供的断言方法,允许定制失败时的处理
15 | public func MBAssert(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = "assertion failure.", file: StaticString = #file, line: UInt = #line) {
16 | if condition() {
17 | return
18 | }
19 | (_assertHandler ?? _defaultHandler)(message(), file, line)
20 | }
21 |
22 | /// 自定义断言失败的处理,默认 NSLog
23 | public func MBAssertSetHandler(_ handler: ((_ message: String, _ file: StaticString, _ line: UInt) -> Void)?) {
24 | _assertHandler = handler
25 | }
26 |
27 | private var _assertHandler: ((_ message: String, _ file: StaticString, _ line: UInt) -> Void)?
28 | private func _defaultHandler(_ message: String, _ file: StaticString, _ line: UInt) {
29 | let filename = (file.description as NSString).lastPathComponent
30 | NSLog("%@", "\(filename):\(line):💥 \(message)")
31 | }
32 |
--------------------------------------------------------------------------------
/Packages/AppFramework/Tests/AppFrameworkTests/AppErrorTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | AppErrorTests.swift
3 | AppFramework
4 |
5 | Copyright © 2023 BB9z.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | import AppFramework
13 | import XCTest
14 |
15 | final class AppErrorTests: XCTestCase {
16 |
17 | func testMessageError() {
18 | let message = "This is an error message"
19 | let error = AppError.message(message)
20 | XCTAssertEqual(error.errorDescription, message)
21 | }
22 |
23 | func testIsCancel() {
24 | XCTAssertFalse(AppError.isCancel(
25 | AppError.message("test")
26 | ))
27 | XCTAssertTrue(AppError.isCancel(
28 | NSError(domain: NSURLErrorDomain, code: NSURLErrorCancelled, userInfo: nil)
29 | ))
30 | XCTAssertFalse(AppError.isCancel(
31 | NSError(domain: NSURLErrorDomain, code: NSURLErrorBadURL, userInfo: nil)
32 | ))
33 | XCTAssertTrue(AppError.isCancel(
34 | CancellationError()
35 | ))
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Packages/AppFramework/Tests/AppFrameworkTests/Assets/Test.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Packages/AppFramework/Tests/AppFrameworkTests/Assets/Test.xcassets/zs_test_1.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "image_solid_123456.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Packages/AppFramework/Tests/AppFrameworkTests/Assets/Test.xcassets/zs_test_1.imageset/image_solid_123456.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/Packages/AppFramework/Tests/AppFrameworkTests/Assets/Test.xcassets/zs_test_1.imageset/image_solid_123456.png
--------------------------------------------------------------------------------
/Packages/AppFramework/Tests/AppFrameworkTests/Extensions/CollectionExTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | CollectionExTests.swift
3 | AppFramework
4 |
5 | Copyright © 2023 BB9z.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | import AppFramework
13 | import XCTest
14 |
15 | class CollectionExTests: XCTestCase {
16 |
17 | func testSequenceUniqued() {
18 | XCTAssertEqual(
19 | [1, 2, 3, 2, 4, 5, 1].uniqued(),
20 | [1, 2, 3, 4, 5]
21 | )
22 |
23 | XCTAssertEqual(
24 | ["1"].uniqued(),
25 | ["1"]
26 | )
27 |
28 | let emptyArray = [Int]()
29 | XCTAssertEqual(emptyArray.uniqued(), [])
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Packages/AppFramework/Tests/AppFrameworkTests/Extensions/UIImageImageSetTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | UIImageImageSetTests.swift
3 | AppFramework
4 |
5 | Copyright © 2023 BB9z.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | import AppFramework
13 | import XCTest
14 | import UIKit
15 |
16 | class UIImageImageSetTests: XCTestCase {
17 | func testInitWithSetNameAndIdentifier() {
18 | var assertCalled = 0
19 | MBAssertSetHandler { message, _, _ in
20 | assertCalled += 1
21 | print(message)
22 | }
23 | defer {
24 | MBAssertSetHandler(nil)
25 | }
26 |
27 | // OK
28 | XCTAssertNotNil(UIImage(setName: "zs_test", identifier: "1", in: .module))
29 | XCTAssertEqual(0, assertCalled)
30 |
31 | // Bad set name
32 | XCTAssertNil(UIImage(setName: "test", identifier: "1", in: .module))
33 | XCTAssertEqual(1, assertCalled)
34 |
35 | // No existent
36 | XCTAssertNil(UIImage(setName: "zs_test", identifier: 2, in: .module))
37 | XCTAssertEqual(2, assertCalled)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Packages/AppFramework/Tests/AppFrameworkTests/Helper/XCTest+.swift:
--------------------------------------------------------------------------------
1 | /*
2 | XCTest+.swift
3 | AppFramework
4 |
5 | Copyright © 2023 BB9z.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | import XCTest
13 |
14 | extension XCTestCase {
15 | func noBlockingWait(_ time: TimeInterval = 0.1) {
16 | let waiter = XCTWaiter()
17 | let exp = XCTestExpectation() // Don't use self
18 | DispatchQueue.main.asyncAfter(deadline: .now() + time) {
19 | exp.fulfill()
20 | }
21 | waiter.wait(for: [exp], timeout: 10) // No more than 10s
22 | print("Test> noBlockingWait end.")
23 | }
24 | }
25 |
26 | extension XCTestExpectation {
27 | /// Return the expectation that is not intended to happen.
28 | @discardableResult
29 | func inverted() -> Self {
30 | isInverted = true
31 | return self
32 | }
33 | }
34 |
35 | #if canImport(UIKit)
36 | import UIKit
37 |
38 | extension XCTestCase {
39 | /// 模拟触发 UIControl 的 touchUpInside 事件
40 | func tap(_ control: UIControl) {
41 | control.sendActions(for: .touchUpInside)
42 | }
43 | }
44 |
45 | #endif
46 |
--------------------------------------------------------------------------------
/Packages/AppFramework/run_unit_tests.command:
--------------------------------------------------------------------------------
1 | #! /bin/zsh
2 | # Test Package
3 | #
4 | # Copyright © 2023 BB9z.
5 | # https://github.com/BB9z/iOS-Project-Template
6 | #
7 | # The MIT License
8 | # https://opensource.org/licenses/MIT
9 |
10 | set -euo pipefail
11 | cd "$(dirname "$0")"
12 |
13 | formatter=xcpretty
14 | if [ -x "$(command -v xcbeautify)" ]; then
15 | formatter=xcbeautify
16 | fi
17 |
18 | name="AppFramework"
19 |
20 | echo "Test Release Build"
21 | xcodebuild -workspace "$name.xcworkspace" -scheme "$name" -destination 'generic/platform=iOS' -configuration Release build | $formatter
22 | echo "-----------"
23 |
24 | echo "Run Unit Tests"
25 | xcodebuild -workspace "$name.xcworkspace" -scheme "$name" -destination "platform=macOS,arch=x86_64,variant=Mac Catalyst" -derivedDataPath Build -enableCodeCoverage YES test | $formatter
26 | echo "-----------"
27 |
28 | echo "Test Coverage"
29 | xcrun xccov view --only-targets --report Build/Logs/Test/*.xcresult
30 |
31 | coverage=$(xcrun xccov view --report Build/Logs/Test/*.xcresult --json | python3 -c "import json,sys;obj=json.load(sys.stdin);print(obj['lineCoverage']);")
32 | echo "Test coverage: $coverage"
33 |
34 | if (( $(echo "$coverage > 0.95" | bc -l) )); then
35 | echo "✅ Coverage OK"
36 | else
37 | echo "🛑 Coverage Bad"
38 | exit 1
39 | fi
40 |
--------------------------------------------------------------------------------
/Packages/AssociatedObject/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright © 2020-2022 BB9z
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Packages/AssociatedObject/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.5
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "B9AssociatedObject",
8 | products: [
9 | // Products define the executables and libraries a package produces, and make them visible to other packages.
10 | .library(
11 | name: "B9AssociatedObject",
12 | targets: ["B9AssociatedObject"]),
13 | ],
14 | dependencies: [
15 | // Dependencies declare other packages that this package depends on.
16 | // .package(url: /* package url */, from: "1.0.0"),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
20 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
21 | .target(
22 | name: "B9AssociatedObject",
23 | dependencies: []),
24 | .testTarget(
25 | name: "MainTests",
26 | dependencies: ["B9AssociatedObject"]),
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/Packages/AssociatedObject/Tests/MainTests/MainTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import B9AssociatedObject
3 |
4 | enum EnumType: Equatable, CustomDebugStringConvertible {
5 | case one
6 | case two
7 |
8 | var debugDescription: String {
9 | switch self {
10 | case .one:
11 | return ""
12 | case .two:
13 | return ""
14 | }
15 | }
16 | }
17 |
18 | let enumAssociation = AssociatedObject()
19 |
20 | class A {}
21 |
22 | extension A {
23 | var enumValue: EnumType? {
24 | get { enumAssociation[self] }
25 | set { enumAssociation[self] = newValue }
26 | }
27 | }
28 |
29 | final class AssociatedObjectTests: XCTestCase {
30 | func testSwiftEnum() {
31 | let a1 = A()
32 | let a2 = A()
33 | assert(a1.enumValue == nil)
34 | a1.enumValue = .one
35 | a2.enumValue = .two
36 | debugPrint(a1.enumValue as Any, a2.enumValue as Any)
37 | assert(a1.enumValue == .one)
38 | assert(a2.enumValue == .two)
39 | assert(a1.enumValue != a2.enumValue)
40 | a1.enumValue = .two
41 | assert(a1.enumValue == .two)
42 | assert(a1.enumValue == a2.enumValue)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Packages/B9Foundation/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 |
--------------------------------------------------------------------------------
/Packages/B9Foundation/.swiftpm/xcode/package.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Packages/B9Foundation/B9Foundation.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Packages/B9Foundation/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let mainName = "B9Foundation"
7 |
8 | let package = Package(
9 | name: mainName,
10 | products: [
11 | // Products define the executables and libraries a package produces, and make them visible to other packages.
12 | .library(
13 | name: mainName,
14 | targets: [mainName])
15 | ],
16 | dependencies: [
17 | // Dependencies declare other packages that this package depends on.
18 | // .package(url: /* package url */, from: "1.0.0"),
19 | ],
20 | targets: [
21 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
22 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
23 | .target(
24 | name: mainName,
25 | dependencies: []),
26 | .testTarget(
27 | name: mainName + "Tests",
28 | dependencies: [
29 | Target.Dependency(stringLiteral: mainName)
30 | ],
31 | resources: [
32 | .process("Resources"),
33 | ]),
34 | ]
35 | )
36 |
--------------------------------------------------------------------------------
/Packages/B9Foundation/README.md:
--------------------------------------------------------------------------------
1 | # B9Foundation
2 |
--------------------------------------------------------------------------------
/Packages/B9Foundation/Sources/B9Foundation/DateMock.swift:
--------------------------------------------------------------------------------
1 | /*
2 | DateMock
3 | B9Foundation
4 |
5 | Copyright © 2023 BB9z.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | import Foundation
13 |
14 | extension Date {
15 | /// Returns a date instance that represents the current date and time, at the moment of access.
16 | ///
17 | /// It can be overwritten during testing.
18 | /// ```
19 | /// @testable import B9Foundation
20 | /// Date.overwriteCurrent(...)
21 | /// ```
22 | public static var current: Date {
23 | injectedCurrent?() ?? Date()
24 | }
25 |
26 | private static var injectedCurrent: (() -> Date)?
27 |
28 | /// Overwrite `current` with the given closure, set `nil` to reset.
29 | internal static func overwriteCurrent(_ block: (() -> Date)?) {
30 | injectedCurrent = block
31 | }
32 |
33 | /// Overwrite `current` with the given date value
34 | internal static func overwriteCurrent(_ value: Date) {
35 | injectedCurrent = { value }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Packages/B9Foundation/Sources/B9Foundation/Result+.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Result+
3 | B9Foundation
4 |
5 | Copyright © 2023 BB9z.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | import Foundation
13 |
14 | public extension Result {
15 | /// Is success or not?
16 | var isSuccess: Bool {
17 | if case .success = self {
18 | return true
19 | }
20 | return false
21 | }
22 |
23 | /// Conveniently return the error object, or nil if the result is success.
24 | var error: Error? {
25 | if case .failure(let failure) = self {
26 | return failure
27 | }
28 | return nil
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Packages/B9Foundation/Sources/B9Foundation/UIResponder+.swift:
--------------------------------------------------------------------------------
1 | /*
2 | UIResponder+
3 | B9Foundation
4 |
5 | Copyright © 2022 BB9z.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | import UIKit
13 |
14 | extension UIResponder {
15 | /**
16 | Returns the nearest view controller to the receiver in the responder chain.
17 |
18 | 返回响应者链中最接近的 view controller
19 | */
20 | public var viewController: UIViewController? {
21 | next(type: UIViewController.self)
22 | }
23 |
24 | /**
25 | Traverse the responder chain to find the first object of the specified type in it.
26 |
27 | 遍历响应者链,从中查找第一个给定类型的对象。
28 |
29 | - Returns: The nearest specified type responder to the receiver, returns nil if not found.
30 | 最接近接受者的指定类型对象,找不到返回 nil
31 | */
32 | public func next(type: T.Type) -> T? {
33 | var responder = self
34 | while let next = responder.next {
35 | if let result = next as? T {
36 | return result
37 | }
38 | responder = next
39 | }
40 | return nil
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Packages/B9Foundation/Tests/B9FoundationTests/Helper.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Helper.swift
3 | B9Foundation
4 |
5 | Copyright © 2023 BB9z.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | import XCTest
13 |
14 | extension XCTestCase {
15 | /// Load test image in test bundle
16 | func loadTestImage(named: String, extension: String = "png", directory: String? = nil) -> UIImage {
17 | guard let url = Bundle.module.url(forResource: named, withExtension: `extension`, subdirectory: directory),
18 | let image = UIImage(contentsOfFile: url.path) else {
19 | fatalError("Unable load test image: \(named).")
20 | }
21 | return image
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Packages/B9Foundation/Tests/B9FoundationTests/Resources/image_diff_size.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/Packages/B9Foundation/Tests/B9FoundationTests/Resources/image_diff_size.png
--------------------------------------------------------------------------------
/Packages/B9Foundation/Tests/B9FoundationTests/Resources/image_solid_123456.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/Packages/B9Foundation/Tests/B9FoundationTests/Resources/image_solid_123456.png
--------------------------------------------------------------------------------
/Packages/B9Foundation/Tests/B9FoundationTests/Resources/image_solid_123456_diff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BB9z/iOS-Project-Template/e1876ee6aa7d08b95c68bf18ac85e3628de3ba93/Packages/B9Foundation/Tests/B9FoundationTests/Resources/image_solid_123456_diff.png
--------------------------------------------------------------------------------
/Packages/B9Foundation/Tests/B9FoundationTests/ResultExTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | ResultExTests.swift
3 | B9Foundation
4 |
5 | Copyright © 2023 BB9z.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | import B9Foundation
13 | import XCTest
14 |
15 | class ResultExTests: XCTestCase {
16 | enum TestError: Error {
17 | case test
18 | }
19 |
20 | func testIsSuccessGetter() {
21 | let result1 = Result.success(true)
22 | XCTAssertTrue(result1.isSuccess)
23 |
24 | let result2 = Result.failure(NSError())
25 | XCTAssertFalse(result2.isSuccess)
26 | }
27 |
28 | func testErrorGetter() {
29 | let result1 = Result.success(true)
30 | XCTAssertNil(result1.error)
31 |
32 | let result2 = Result.failure(TestError.test)
33 | XCTAssertNotNil(result2.error)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Packages/B9Foundation/run_unit_tests.command:
--------------------------------------------------------------------------------
1 | #! /bin/zsh
2 | # Test Package
3 | #
4 | # Copyright © 2023 BB9z.
5 | # https://github.com/BB9z/iOS-Project-Template
6 | #
7 | # The MIT License
8 | # https://opensource.org/licenses/MIT
9 |
10 | set -euo pipefail
11 | cd "$(dirname "$0")"
12 |
13 | formatter=xcpretty
14 | if [ -x "$(command -v xcbeautify)" ]; then
15 | formatter=xcbeautify
16 | fi
17 |
18 | name="B9Foundation"
19 |
20 | echo "Test Release Build"
21 | xcodebuild -workspace "$name.xcworkspace" -scheme "$name" -destination 'generic/platform=iOS' -configuration Release build | $formatter
22 | echo "-----------"
23 |
24 | echo "Run Unit Tests"
25 | xcodebuild -workspace "$name.xcworkspace" -scheme "$name" -destination "platform=macOS,arch=x86_64,variant=Mac Catalyst" -derivedDataPath Build -enableCodeCoverage YES test | $formatter
26 | echo "-----------"
27 |
28 | echo "Test Coverage"
29 | xcrun xccov view --only-targets --report Build/Logs/Test/*.xcresult
30 |
31 | coverage=$(xcrun xccov view --report Build/Logs/Test/*.xcresult --json | python3 -c "import json,sys;obj=json.load(sys.stdin);print(obj['lineCoverage']);")
32 | echo "Test coverage: $coverage"
33 |
34 | if (( $(echo "$coverage > 0.95" | bc -l) )); then
35 | echo "✅ Coverage OK"
36 | else
37 | echo "🛑 Coverage Bad"
38 | exit 1
39 | fi
40 |
--------------------------------------------------------------------------------
/Packages/BuildSystem/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Packages/BuildSystem/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.7
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | // https://developer.apple.com/documentation/swift_packages/package
7 | let package = Package(
8 | name: "BuildSystem",
9 | platforms: [.macOS(.v13)],
10 | products: [
11 | // https://developer.apple.com/documentation/swift_packages/product
12 | // Products define the executables and libraries a package produces, and make them visible to other packages.
13 | .executable(name: "BuildSystem", targets: ["BuildSystem"]),
14 | ],
15 | dependencies: [
16 | // https://developer.apple.com/documentation/swift_packages/target/dependency
17 | // Dependencies declare other packages that this package depends on.
18 | ],
19 | targets: [
20 | // https://developer.apple.com/documentation/swift_packages/target
21 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
22 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
23 | .executableTarget(name: "BuildSystem", dependencies: []),
24 | .testTarget(name: "ScriptTests", dependencies: ["BuildSystem"]),
25 | ]
26 | )
27 |
--------------------------------------------------------------------------------
/Packages/BuildSystem/Sources/BuildSystem/Configuration.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Configuration.swift
3 | Script
4 |
5 | Copyright © 2022 BB9z.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | import Foundation
13 | import RegexBuilder // Xcode 14.1+
14 |
15 | /**
16 | 运行脚本的配置
17 |
18 | 🔰 按需修改
19 | */
20 | enum Configuration {
21 | /// 脚本运行爆出的警告过多则报错
22 | /// 设为 0 禁用
23 | static let limitWarningCount: Int = 10
24 |
25 | /// Info.plist 的相对路径
26 | static let infoPlistPath = "App/Info.plist"
27 |
28 | /// 开关:编译次数计数与更新
29 | static let isBuildCountEnabled = true
30 |
31 | /// 仅在打包(Archive)时更新版本号
32 | static let buildCountOnlyUpdateWhenArchive = true
33 |
34 | /// 开关:搜寻代码中特定注释并输出
35 | static let isHighlightCommentsEnabled = true
36 |
37 | /// 特定注释的正则
38 | static let highlightCommentsRegex = Regex {
39 | "//"
40 | ZeroOrMore(.whitespace)
41 | Capture {
42 | ChoiceOf {
43 | "todo"
44 | "fixme"
45 | }
46 | }
47 | ":"
48 | ZeroOrMore(.whitespace)
49 | Capture {
50 | OneOrMore(.any)
51 | }
52 | Anchor.endOfLine
53 | }.ignoresCase()
54 | }
55 |
--------------------------------------------------------------------------------
/Packages/BuildSystem/Sources/BuildSystem/Support/GError.swift:
--------------------------------------------------------------------------------
1 | /*
2 | GError.swift
3 | BuildSystem
4 |
5 | Copyright © 2022 BB9z.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | import Foundation
13 |
14 | enum GError: LocalizedError {
15 |
16 | case message(_ message: String)
17 |
18 | var errorDescription: String? {
19 | switch self {
20 | case let .message(text):
21 | return text
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Packages/BuildSystem/Sources/BuildSystem/main.swift:
--------------------------------------------------------------------------------
1 | /*
2 | BuildSystem
3 | Script
4 |
5 | Copyright © 2022 BB9z.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | import Foundation
13 |
14 | /**
15 | 自定义的构建过程
16 |
17 | 🔰 按需修改。目前提供的功能是高亮特殊注释,构建数量计数并更新
18 | */
19 | func main() {
20 | let context = XCContext()
21 |
22 | let startTime = CFAbsoluteTimeGetCurrent()
23 | defer {
24 | let duration = CFAbsoluteTimeGetCurrent() - startTime
25 | let durationMessage = "执行耗时:\(String(format: "%.2f", duration))s"
26 | if duration > 0.2 {
27 | Log.warning(durationMessage)
28 | } else {
29 | Log.info(durationMessage)
30 | }
31 | }
32 | Log.info("Current Directory: \(context.sourceRoot)")
33 | do {
34 | try BuildCount.run(context)
35 | try HighlightComments.run(context)
36 | } catch {
37 | Log.error(error.localizedDescription)
38 | }
39 | }
40 |
41 | main()
42 |
--------------------------------------------------------------------------------
/Packages/BuildSystem/Tests/ScriptTests/CommandLineTests.swift:
--------------------------------------------------------------------------------
1 | /*
2 | CommandLineTests.swift
3 | Script
4 |
5 | Copyright © 2022 BB9z.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | @testable import BuildSystem
13 | import XCTest
14 |
15 | final class CommandLineTests: XCTestCase {
16 |
17 | func testCommandURL() {
18 | let url = CommandLine.url
19 | XCTAssertEqual("xctest", url.lastPathComponent, "Should be xctest during testing")
20 | XCTAssertEqual(url.path, CommandLine.arguments[0])
21 | }
22 |
23 | func testRunNoExistsCommand() {
24 | let result = CommandLine.run(["_b9_no_exists_"])
25 | XCTAssertEqual(127, result.status)
26 | XCTAssertEqual("env: _b9_no_exists_: No such file or directory", result.output)
27 | }
28 |
29 | func testRunSuccess() {
30 | let result = CommandLine.run(["ls"])
31 | XCTAssertEqual(0, result.status)
32 | let lineCount = result.output.split(separator: "\n").count
33 | XCTAssertGreaterThan(lineCount, 4, "Should have many files in build directory")
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Packages/Condition/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright © 2022 BB9z
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Packages/Condition/README.md:
--------------------------------------------------------------------------------
1 | # B9Condition
2 |
3 | [](https://swift.org)
4 | [](https://swift.org/package-manager)
5 | [](https://github.com/b9swift/Condition/actions)
6 | [](https://gitee.com/b9swift/Condition)
7 | [](https://github.com/b9swift/Condition)
8 |
9 | 维护一组状态,当满足特定状态时执行相应的监听
10 | Maintains a set of states that allows it to perform observation actions when the specific states are satisfied.
11 |
12 | ## 集成
13 |
14 | 使用 Swift Package Manager 或手工导入。
15 |
16 | You can also use [GitHub source](https://github.com/b9swift/Condition).
17 |
18 | ## Installation
19 |
20 | You can use either Swift Package Manager or manual importing to add this package or module to your project.
21 |
22 | 你也可以使用 [gitee 镜像](https://gitee.com/b9swift/Condition)。
23 |
--------------------------------------------------------------------------------
/Packages/Debug/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright © 2022 BB9z
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Packages/Debug/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.0
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "B9Debug",
8 | products: [
9 | // Products define the executables and libraries a package produces, and make them visible to other packages.
10 | .library(name: "B9Debug", type: .static, targets: ["B9Debug"])
11 | ],
12 | dependencies: [
13 | // Dependencies declare other packages that this package depends on.
14 | // .package(url: /* package url */, from: "1.0.0"),
15 | ],
16 | targets: [
17 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
18 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
19 | .target(name: "B9Debug")
20 | ]
21 | )
22 |
--------------------------------------------------------------------------------
/Packages/Debug/Sources/B9Debug/B9Debug.m:
--------------------------------------------------------------------------------
1 |
2 | #import "B9Debug.h"
3 |
4 | #if DEBUG
5 | void ThrowExceptionToPause(void) {
6 | @try {
7 | @throw [NSException exceptionWithName:@"Debugger" reason:nil userInfo:nil];
8 | }
9 | @catch (NSException *exception) { }
10 | }
11 | #endif
12 |
--------------------------------------------------------------------------------
/Packages/Debug/Sources/B9Debug/include/B9Debug.h:
--------------------------------------------------------------------------------
1 | /*
2 | B9Debug
3 |
4 | Copyright © 2022 BB9z.
5 | https://github.com/b9swift/B9Debug
6 |
7 | The MIT License
8 | https://opensource.org/licenses/MIT
9 | */
10 |
11 | #import
12 |
13 | /**
14 | 抛出一个 objc 异常以暂停,用以在调试时提示需要注意的事
15 |
16 | 仅在 DEBUG 环境可用,开启 Objective-C 异常断点后可断住
17 |
18 | Throw an objc exception to pause to indicate things for attention while debugging.
19 |
20 | Only available in DEBUG configuration. Active when the Objective-C exception breakpoint is turned on.
21 | */
22 | #if DEBUG
23 | FOUNDATION_EXPORT void ThrowExceptionToPause(void);
24 | #else
25 | NS_INLINE void ThrowExceptionToPause(void) {
26 | // Do nothing
27 | };
28 | #endif
29 |
--------------------------------------------------------------------------------
/Packages/Debugger/README.md:
--------------------------------------------------------------------------------
1 | # Debugger
2 |
3 | 应用内建的调试、辅助工具集。
4 |
5 | 部分功能:
6 |
7 | - 调试浮窗,可移动、调整大小、显示/关闭、展示自定义 view;
8 | - 浮窗中可以触发各种命令,来源有:内建命令,自定义全局命令,从上下文中(当前各层 view controller 中)获取的自定义命令;
9 | - 对象检查,支持列表自动发现;
10 | - 浮窗显示按钮等……
11 |
--------------------------------------------------------------------------------
/Packages/Debugger/Sources/Debugger/UI/DescriptionViewController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | DescriptionViewController.swift
3 | Debugger
4 |
5 | Copyright © 2022 BB9z.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 |
12 | import UIKit
13 |
14 | /// 展示文本内容的页面
15 | internal final class DescriptionViewController: UIViewController {
16 | static func new() -> DescriptionViewController {
17 | // swiftlint:disable:next force_cast
18 | Debugger.storyboard.instantiateViewController(withIdentifier: "DescriptionViewController") as! DescriptionViewController
19 | }
20 |
21 | var item: String!
22 | @IBOutlet private weak var textView: UITextView!
23 |
24 | override func viewDidLoad() {
25 | super.viewDidLoad()
26 | textView.text = item
27 | }
28 |
29 | @IBAction private func onCopy(_ sender: Any) {
30 | UIPasteboard.general.string = item
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Packages/InterfaceApp/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 |
--------------------------------------------------------------------------------
/Packages/InterfaceApp/.swiftpm/xcode/package.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Packages/InterfaceApp/InterfaceApp.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Packages/InterfaceApp/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let mainName = "InterfaceApp"
7 |
8 | let package = Package(
9 | name: mainName,
10 | products: [
11 | // Products define the executables and libraries a package produces, and make them visible to other packages.
12 | .library(
13 | name: mainName,
14 | targets: [mainName])
15 | ],
16 | dependencies: [
17 | // Dependencies declare other packages that this package depends on.
18 | // .package(url: /* package url */, from: "1.0.0"),
19 | ],
20 | targets: [
21 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
22 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
23 | .target(
24 | name: mainName,
25 | dependencies: []),
26 | .testTarget(
27 | name: mainName + "Tests",
28 | dependencies: [
29 | Target.Dependency(stringLiteral: mainName)
30 | ]
31 | )
32 | ]
33 | )
34 |
--------------------------------------------------------------------------------
/Packages/InterfaceApp/README.md:
--------------------------------------------------------------------------------
1 | # InterfaceApp
2 |
3 | 应用侧实现的界面,供 AppFramework 使用。
4 |
5 | 界面统一前缀:`IA`。
6 |
--------------------------------------------------------------------------------
/Packages/InterfaceApp/Sources/InterfaceApp/IAAccount.swift:
--------------------------------------------------------------------------------
1 | /*!
2 | IAAccount
3 | InterfaceApp
4 |
5 | Copyright © 2023 BB9z.
6 | https://github.com/BB9z/iOS-Project-Template
7 |
8 | The MIT License
9 | https://opensource.org/licenses/MIT
10 | */
11 | import Foundation
12 |
13 | /**
14 | 应用界面:账户
15 |
16 | 设计备忘:
17 | - 使用地址,而非 Equatable 或者 id 比较相同
18 | */
19 | public protocol IAAccount: AnyObject {
20 | var id: String { get }
21 |
22 | /// 成为当前用户时执行的操作,应当在主线程执行
23 | func didLogin()
24 |
25 | /// 用户登出时执行的操作,应当在主线程执行
26 | func didLogout()
27 | }
28 |
--------------------------------------------------------------------------------
/Packages/InterfaceApp/run_unit_tests.command:
--------------------------------------------------------------------------------
1 | #! /bin/zsh
2 | # Test Package
3 | #
4 | # Copyright © 2023 BB9z.
5 | # https://github.com/BB9z/iOS-Project-Template
6 | #
7 | # The MIT License
8 | # https://opensource.org/licenses/MIT
9 |
10 | set -euo pipefail
11 | cd "$(dirname "$0")"
12 |
13 | formatter=xcpretty
14 | if [ -x "$(command -v xcbeautify)" ]; then
15 | formatter=xcbeautify
16 | fi
17 |
18 | name="InterfaceApp"
19 |
20 | echo "Test Release Build"
21 | xcodebuild -workspace "$name.xcworkspace" -scheme "$name" -destination 'generic/platform=iOS' -configuration Release build | $formatter
22 | echo "-----------"
23 |
24 | echo "Run Unit Tests"
25 | xcodebuild -workspace "$name.xcworkspace" -scheme "$name" -destination "platform=macOS,arch=x86_64,variant=Mac Catalyst" -derivedDataPath Build -enableCodeCoverage YES test | $formatter
26 | echo "-----------"
27 |
28 | echo "Test Coverage"
29 | xcrun xccov view --only-targets --report Build/Logs/Test/*.xcresult
30 |
31 | coverage=$(xcrun xccov view --report Build/Logs/Test/*.xcresult --json | python3 -c "import json,sys;obj=json.load(sys.stdin);print(obj['lineCoverage']);")
32 | echo "Test coverage: $coverage"
33 |
34 | if (( $(echo "$coverage > 0.95" | bc -l) )); then
35 | echo "✅ Coverage OK"
36 | else
37 | echo "🛑 Coverage Bad"
38 | exit 1
39 | fi
40 |
--------------------------------------------------------------------------------
/Packages/MulticastDelegate/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright © 2019-2021 BB9z
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Packages/MulticastDelegate/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "B9MulticastDelegate",
8 | products: [
9 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
10 | .library(
11 | name: "B9MulticastDelegate",
12 | targets: ["B9MulticastDelegate"]),
13 | ],
14 | dependencies: [
15 | // Dependencies declare other packages that this package depends on.
16 | // .package(url: /* package url */, from: "1.0.0"),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
21 | .target(
22 | name: "B9MulticastDelegate",
23 | dependencies: []),
24 | .testTarget(
25 | name: "B9MulticastDelegateTests",
26 | dependencies: ["B9MulticastDelegate"]),
27 | ]
28 | )
29 | package.swiftLanguageVersions = [.v5]
30 |
--------------------------------------------------------------------------------
/Packages/Pulse/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Copyright (c) 2020-2021 Alexander Grebenyuk
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Packages/Pulse/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.6
2 |
3 | import PackageDescription
4 |
5 | let package = Package(
6 | name: "Pulse",
7 | platforms: [
8 | .iOS(.v13),
9 | .tvOS(.v13),
10 | .macOS(.v11),
11 | .watchOS(.v7)
12 | ],
13 | products: [
14 | .library(name: "Pulse", targets: ["Pulse"]),
15 | .library(name: "PulseUI", targets: ["PulseUI"]),
16 | .library(name: "PulseLogHandler", targets: ["PulseLogHandler"])
17 | ],
18 | dependencies: [
19 | .package(url: "https://github.com/apple/swift-log.git", from: "1.2.0")
20 | ],
21 | targets: [
22 | .target(name: "Pulse"),
23 | .target(name: "PulseUI", dependencies: ["Pulse"]),
24 | .target(name: "PulseLogHandler", dependencies: [.product(name: "Logging", package: "swift-log"), "Pulse"]),
25 | ]
26 | )
27 |
--------------------------------------------------------------------------------
/Packages/Pulse/README.md:
--------------------------------------------------------------------------------
1 | # Pulse
2 |
3 | This is a lite package, please check the original repository at https://github.com/kean/Pulse
4 |
5 | version: 2.1.2
6 |
--------------------------------------------------------------------------------
/Packages/Pulse/Sources/Pulse/Helpers/LoggerSession.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2020–2022 Alexander Grebenyuk (github.com/kean).
4 |
5 | #if !os(macOS) && !targetEnvironment(macCatalyst) && swift(>=5.7)
6 | import Foundation
7 | #else
8 | @preconcurrency import Foundation
9 | #endif
10 |
11 | extension LoggerStore {
12 | public struct Session: Sendable {
13 | public let id: UUID
14 |
15 | public init(id: UUID = UUID()) {
16 | self.id = id
17 | }
18 |
19 | /// Returns current log session.
20 | public static var current = Session()
21 |
22 | /// Starts a new log session.
23 | public static func startSession() {
24 | LoggerStore.Session.current = Session()
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Packages/Pulse/Sources/Pulse/RemoteLogger/RemoteLogger-Store.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2020–2022 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 | import Network
7 | import Combine
8 |
9 | @available(iOS 14.0, tvOS 14.0, *)
10 | extension RemoteLogger {
11 | /// Applies the given event to the store.
12 | public static func process(_ event: LoggerStore.Event, store: LoggerStore) {
13 | store.handleExternalEvent(event)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Packages/Pulse/Sources/PulseUI/Extensions/PulseCore+Extensions.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2020–2022 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 | import Pulse
7 |
8 | extension NetworkTaskEntity {
9 | var requestFileViewerContext: FileViewerViewModel.Context {
10 | FileViewerViewModel.Context(
11 | contentType: originalRequest?.contentType,
12 | originalSize: requestBodySize,
13 | metadata: metadata,
14 | isResponse: false,
15 | error: nil
16 | )
17 | }
18 |
19 | var responseFileViewerContext: FileViewerViewModel.Context {
20 | FileViewerViewModel.Context(
21 | contentType: response?.contentType,
22 | originalSize: responseBodySize,
23 | metadata: metadata,
24 | isResponse: true,
25 | error: decodingError
26 | )
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Packages/Pulse/Sources/PulseUI/Extensions/UITableView+Extensions.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2020–2022 Alexander Grebenyuk (github.com/kean).
4 |
5 | #if os(iOS)
6 |
7 | import UIKit
8 |
9 | extension UITableView {
10 | func apply(diff: CollectionDifference, _ closure: () -> Void) {
11 | var deletes: [IndexPath] = []
12 | var inserts: [IndexPath] = []
13 |
14 | for update in diff.inferringMoves() {
15 | switch update {
16 | case .remove(let offset, _, let move):
17 | if move == nil {
18 | deletes.append(IndexPath(row: offset, section: 0))
19 | }
20 | case .insert(let offset, _, let move):
21 | // If there's no move, it's a true insertion and not the result of a move.
22 | if move == nil {
23 | inserts.append(IndexPath(row: offset, section: 0))
24 | }
25 | }
26 | }
27 |
28 | performBatchUpdates {
29 | closure()
30 | insertRows(at: inserts, with: .right)
31 | deleteRows(at: deletes, with: .left)
32 | }
33 | }
34 | }
35 |
36 | #endif
37 |
--------------------------------------------------------------------------------
/Packages/Pulse/Sources/PulseUI/Features/Console/ConsoleView-tvos.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2020–2022 Alexander Grebenyuk (github.com/kean).
4 |
5 | import SwiftUI
6 | import CoreData
7 | import Pulse
8 | import Combine
9 |
10 | #if os(tvOS)
11 |
12 | public struct ConsoleView: View {
13 | @ObservedObject var viewModel: ConsoleViewModel
14 | @State private var isShowingFiltersView = false
15 | @State private var isShowingRemoveConfirmationAlert = false
16 | @State private var isStoreArchived = false
17 |
18 | public init(store: LoggerStore = .shared) {
19 | self.viewModel = ConsoleViewModel(store: store)
20 | }
21 |
22 | init(viewModel: ConsoleViewModel) {
23 | self.viewModel = viewModel
24 | }
25 |
26 | public var body: some View {
27 | List {
28 | ConsoleMessagesForEach(messages: viewModel.entities)
29 | }
30 | .onAppear(perform: viewModel.onAppear)
31 | .onDisappear(perform: viewModel.onDisappear)
32 | }
33 | }
34 |
35 | #if DEBUG
36 | struct ConsoleView_Previews: PreviewProvider {
37 | static var previews: some View {
38 | ConsoleView(viewModel: .init(store: .mock))
39 | }
40 | }
41 | #endif
42 | #endif
43 |
--------------------------------------------------------------------------------
/Packages/Pulse/Sources/PulseUI/Features/Insights/NetworkInsightsRequestsList.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2020–2022 Alexander Grebenyuk (github.com/kean).
4 |
5 | import SwiftUI
6 | import CoreData
7 | import Pulse
8 | import Combine
9 |
10 | #if os(iOS)
11 |
12 | struct NetworkInsightsRequestsList: View {
13 | @ObservedObject var viewModel: NetworkInsightsRequestsListViewModel
14 |
15 | public var body: some View {
16 | ConsoleTableView(
17 | header: { EmptyView() },
18 | viewModel: viewModel.table,
19 | detailsViewModel: viewModel.details
20 | )
21 | }
22 | }
23 |
24 | final class NetworkInsightsRequestsListViewModel: ObservableObject {
25 | let table: ConsoleTableViewModel
26 | let details: ConsoleDetailsRouterViewModel
27 |
28 | init(tasks: [NetworkTaskEntity]) {
29 | self.table = ConsoleTableViewModel(searchCriteriaViewModel: nil)
30 | self.table.entities = tasks
31 | self.details = ConsoleDetailsRouterViewModel()
32 | }
33 | }
34 |
35 | #endif
36 |
--------------------------------------------------------------------------------
/Packages/Pulse/Sources/PulseUI/Features/Main/MainView-watchos.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2020–2022 Alexander Grebenyuk (github.com/kean).
4 |
5 | import SwiftUI
6 | import CoreData
7 | import Pulse
8 | import Combine
9 |
10 | #if os(watchOS)
11 |
12 | public struct MainView: View {
13 | @StateObject private var viewModel: MainViewModel
14 |
15 | public init(store: LoggerStore = .shared) {
16 | self._viewModel = StateObject(wrappedValue: .init(store: store, onDismiss: nil))
17 | }
18 |
19 | public var body: some View {
20 | ConsoleView(viewModel: viewModel)
21 | .onDisappear { viewModel.freeMemory() }
22 | }
23 | }
24 |
25 | #if DEBUG
26 | struct MainView_Previews: PreviewProvider {
27 | static var previews: some View {
28 | MainView(store: .mock)
29 | }
30 | }
31 |
32 | #endif
33 |
34 | #endif
35 |
--------------------------------------------------------------------------------
/Packages/Pulse/Sources/PulseUI/Helpers/ManagedObjectsObserver.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2020–2022 Alexander Grebenyuk (github.com/kean).
4 |
5 | import CoreData
6 |
7 | final class ManagedObjectsObserver: NSObject, ObservableObject, NSFetchedResultsControllerDelegate {
8 | private let controller: NSFetchedResultsController
9 | @Published private(set) var objects: [T] = []
10 |
11 | init(context: NSManagedObjectContext, sortDescriptior: NSSortDescriptor) {
12 | let request = NSFetchRequest(entityName: "\(T.self)")
13 | request.fetchBatchSize = 100
14 | request.sortDescriptors = [sortDescriptior]
15 |
16 | self.controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
17 |
18 | super.init()
19 |
20 | self.controller.delegate = self
21 | self.refresh()
22 | }
23 |
24 | private func refresh() {
25 | try? controller.performFetch()
26 | self.objects = controller.fetchedObjects ?? []
27 | }
28 |
29 | func controllerDidChangeContent(_ controller: NSFetchedResultsController) {
30 | self.objects = self.controller.fetchedObjects ?? []
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Packages/Pulse/Sources/PulseUI/Mocks/MockStoreConfiguration.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2020–2022 Alexander Grebenyuk (github.com/kean).
4 |
5 | import Foundation
6 | import Pulse
7 | import CoreData
8 |
9 | #if DEBUG
10 |
11 | enum MockStoreConfiguration {
12 | static let isDelayingLogs = false
13 | static let isIndefinite = false
14 | static let isUsingDefaultStore = false
15 | }
16 |
17 | #endif
18 |
--------------------------------------------------------------------------------
/Packages/Pulse/Sources/PulseUI/Views/Checkbox.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2020–2022 Alexander Grebenyuk (github.com/kean).
4 |
5 | import SwiftUI
6 |
7 | #if os(iOS) || os(macOS)
8 |
9 | struct Checkbox: View {
10 | @Binding var isEnabled: Bool
11 |
12 | var body: some View {
13 | Button(action: { isEnabled.toggle() }) {
14 | Image(systemName: isEnabled ? "checkmark.circle.fill" : "circle")
15 | .font(.system(size: 18))
16 | .foregroundColor(.accentColor)
17 | }.buttonStyle(.plain)
18 | }
19 | }
20 |
21 | struct CheckboxView_Previews: PreviewProvider {
22 | static var previews: some View {
23 | VStack(spacing: 32) {
24 | HStack(spacing: 16) {
25 | Checkbox(isEnabled: .constant(true))
26 | .disabled(false)
27 | Checkbox(isEnabled: .constant(false))
28 | .disabled(false)
29 | }
30 | HStack(spacing: 16) {
31 | Checkbox(isEnabled: .constant(true))
32 | .disabled(true)
33 | Checkbox(isEnabled: .constant(false))
34 | .disabled(true)
35 | }
36 | }
37 | }
38 | }
39 | #endif
40 |
--------------------------------------------------------------------------------
/Packages/Pulse/Sources/PulseUI/Views/InfoRow.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2020–2022 Alexander Grebenyuk (github.com/kean).
4 |
5 | import SwiftUI
6 |
7 | struct InfoRow: View {
8 | let title: String
9 | let details: String?
10 |
11 | var body: some View {
12 | HStack {
13 | Text(title)
14 | .lineLimit(1)
15 | Spacer()
16 | if let details = details {
17 | Text(details)
18 | .lineLimit(1)
19 | .foregroundColor(.secondary)
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Packages/Pulse/Sources/PulseUI/Views/LargeSectionHeader.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2020–2022 Alexander Grebenyuk (github.com/kean).
4 |
5 | import SwiftUI
6 |
7 | #if os(iOS) || os(macOS)
8 |
9 | struct LargeSectionHeader: View {
10 | let title: String
11 | var accessory: (() -> Accessory)?
12 |
13 | var body: some View {
14 | VStack(spacing: 10) {
15 | HStack(alignment: .bottom) {
16 | Text(title)
17 | .bold()
18 | .font(.title)
19 | .padding(.top, 16)
20 | Spacer()
21 | accessory?()
22 | }
23 | Divider()
24 | }.padding(.bottom, 8)
25 | }
26 | }
27 |
28 | extension LargeSectionHeader where Accessory == EmptyView {
29 | init(title: String) {
30 | self.title = title
31 | }
32 | }
33 |
34 | #endif
35 |
--------------------------------------------------------------------------------
/Packages/Pulse/Sources/PulseUI/Views/PDFRepresentedView.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2020–2022 Alexander Grebenyuk (github.com/kean).
4 |
5 | import SwiftUI
6 |
7 | #if os(iOS)
8 | import PDFKit
9 |
10 | struct PDFKitRepresentedView: UIViewRepresentable {
11 | let document: PDFDocument
12 |
13 | func makeUIView(context: Context) -> PDFView {
14 | let pdfView = PDFView()
15 | pdfView.document = document
16 | return pdfView
17 | }
18 |
19 | func updateUIView(_ view: PDFView, context: Context) {
20 | // Do nothing
21 | }
22 | }
23 | #elseif os(macOS)
24 | import PDFKit
25 |
26 | struct PDFKitRepresentedView: NSViewRepresentable {
27 | let document: PDFDocument
28 |
29 | func makeNSView(context: Context) -> PDFView {
30 | let pdfView = PDFView()
31 | pdfView.document = document
32 | pdfView.autoScales = true
33 | return pdfView
34 | }
35 |
36 | func updateNSView(_ view: PDFView, context: Context) {
37 | // Do nothing
38 | }
39 | }
40 | #endif
41 |
--------------------------------------------------------------------------------
/Packages/Pulse/Sources/PulseUI/Views/RichTextView-watchos.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2020–2022 Alexander Grebenyuk (github.com/kean).
4 |
5 | import SwiftUI
6 | import Pulse
7 |
8 | #if os(watchOS) || os(tvOS)
9 |
10 | struct RichTextView: View {
11 | let viewModel: RichTextViewModel
12 | var onToggleExpanded: (() -> Void)?
13 |
14 | var body: some View {
15 | Text(viewModel.text)
16 | }
17 | }
18 |
19 | final class RichTextViewModel: ObservableObject {
20 | let text: String
21 |
22 | init(string: String) {
23 | self.text = string
24 | }
25 |
26 | init(string: NSAttributedString) {
27 | self.text = string.string
28 | }
29 |
30 | convenience init(json: Any, error: NetworkLogger.DecodingError?) {
31 | self.init(string: format(json: json))
32 | }
33 | }
34 |
35 | private func format(json: Any) -> String {
36 | guard let data = try? JSONSerialization.data(withJSONObject: json, options: [.prettyPrinted]) else {
37 | return ""
38 | }
39 | return String(data: data, encoding: .utf8) ?? ""
40 | }
41 |
42 | #endif
43 |
--------------------------------------------------------------------------------
/Packages/Pulse/Sources/PulseUI/Views/Spinner.swift:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2020–2022 Alexander Grebenyuk (github.com/kean).
4 |
5 | import SwiftUI
6 |
7 | #if os(iOS) || os(tvOS)
8 |
9 | import UIKit
10 |
11 | struct Spinner: View {
12 | var body: some View {
13 | if #available(iOS 14.0, tvOS 14.0, *) {
14 | ProgressView()
15 | } else {
16 | ActivityIndicator()
17 | }
18 | }
19 | }
20 |
21 | private struct ActivityIndicator: UIViewRepresentable {
22 | func makeUIView(context: Context) -> UIActivityIndicatorView {
23 | let view = UIActivityIndicatorView()
24 | view.startAnimating()
25 | return view
26 | }
27 |
28 | func updateUIView(_ uiView: UIActivityIndicatorView, context: Context) {
29 | // Do nothing
30 | }
31 | }
32 |
33 | #else
34 |
35 | struct Spinner: View {
36 | var body: some View {
37 | ProgressView()
38 | }
39 | }
40 |
41 | #if DEBUG
42 | struct Spinner_Previews: PreviewProvider {
43 | static var previews: some View {
44 | Spinner()
45 | }
46 | }
47 | #endif
48 |
49 | #endif
50 |
--------------------------------------------------------------------------------
/Packages/swift-log/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | .xcode
6 | DerivedData
7 | .swiftpm
8 |
9 | .SourceKitten/
10 | docs/
11 |
12 | .*.sw?
13 |
--------------------------------------------------------------------------------
/Packages/swift-log/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.0
2 | //===----------------------------------------------------------------------===//
3 | //
4 | // This source file is part of the Swift Logging API open source project
5 | //
6 | // Copyright (c) 2018-2019 Apple Inc. and the Swift Logging API project authors
7 | // Licensed under Apache License v2.0
8 | //
9 | // See LICENSE.txt for license information
10 | // See CONTRIBUTORS.txt for the list of Swift Logging API project authors
11 | //
12 | // SPDX-License-Identifier: Apache-2.0
13 | //
14 | //===----------------------------------------------------------------------===//
15 |
16 | import PackageDescription
17 |
18 | let package = Package(
19 | name: "swift-log",
20 | products: [
21 | .library(name: "Logging", targets: ["Logging"]),
22 | ],
23 | targets: [
24 | .target(
25 | name: "Logging",
26 | dependencies: []
27 | ),
28 | ]
29 | )
30 |
--------------------------------------------------------------------------------
/Packages/swift-log/README.md:
--------------------------------------------------------------------------------
1 | # SwiftLog
2 |
3 | This is a lite package, please check the original repository at https://github.com/apple/swift-log
4 |
5 | version: 1.4.2
6 |
--------------------------------------------------------------------------------
/ci_scripts/BuildCountRecord.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UserBuildRecords
6 |
7 | bb9z
8 |
9 | main
10 | 3
11 |
12 |
13 | Version
14 | 1
15 |
16 |
17 |
--------------------------------------------------------------------------------
/ci_scripts/README.md:
--------------------------------------------------------------------------------
1 | # ci_scripts
2 |
3 | 脚本目录,存放日常用脚本和部分 CI/CD 脚本。目录名以前是 Scripts,为支持 Xcode Cloud 更名为此(Xcode Cloud 定死改不了,弄两个脚本目录也难看)。
4 |
5 | 🎯 开头的 command 脚本是用来快捷执行常用操作的,我一般直接在 Xcode 中右键 "Open with External Editor" 直接运行。如果系统提示未认证的开发者禁止运行,可通过在 Finder(访达)中右键,选择 "打开" 来解除限制。
6 |
7 | ## GitLab CI 脚本
8 |
9 | ### 特色功能
10 |
11 | - 高效的 CocoaPods 配置:自动跳过 install,缓存,按需 repo update;
12 | - 支持自动/手动代码签名,手动代码签名支持 provisioning profile 、证书自动更新;
13 | - dSYMs 符号文件打包;
14 | - fir.im 上传,可选从哪个环境变量载入 token。
15 |
16 | ⚠️ 初次使用请按需修改 deploy 步骤下的 tags,以便 CI 能跑在正确的环境下。
17 |
18 | ### commit message 行为控制
19 |
20 | 除了基本的用 `[ci skip]` 跳过 CI 外,额外添加以下开关:
21 |
22 | - `[ci clean]` 清理编译
23 | - `[ci verbose]` 输出更多信息
24 |
25 | ## Xcode Cloud 脚本
26 |
27 | 只用了 `ci_post_clone.sh`,用以安装 CocoaPods 依赖。
28 |
29 | [Xcode Cloud 使用](https://developer.apple.com/documentation/xcode/writing-custom-build-scripts)
30 |
--------------------------------------------------------------------------------
/ci_scripts/ci_post_clone.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 | # Xcode Cloud 依赖安装脚本
3 | # Copyright © 2022 BB9z.
4 | # https://github.com/BB9z/iOS-Project-Template
5 |
6 | echo "Xcode Cloud: Project Setup."
7 |
8 | set -euo pipefail
9 |
10 | brew install cocoapods
11 | pod install
12 |
--------------------------------------------------------------------------------
/ci_scripts/sort_projects.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 | # 搜寻目录下的 Xcode 项目文件,对项目文件执行排序整理操作
3 | # Copyright © 2018, 2022-2023 BB9z.
4 | # https://github.com/BB9z/iOS-Project-Template
5 |
6 | set -euo pipefail
7 |
8 | readonly ScriptPath=$(dirname $0)
9 |
10 | for file in $(find . -name "*.xcodeproj" -maxdepth 2); do
11 | if [[ "$file" == *Pods.xcodeproj ]]; then
12 | # echo "跳过 pod"
13 | continue
14 | fi
15 | echo "整理: $file"
16 | perl -w "$ScriptPath/sort-Xcode-project-file.pl" "$file"
17 | done
18 |
--------------------------------------------------------------------------------
/ci_scripts/🎯 Pod Install.command:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 | set -euo pipefail
3 | cd "$(dirname "$0")/.."
4 | echo "$PWD"
5 | pod install --verbose || {
6 | say "Install failed"
7 | exit
8 | }
9 | ./ci_scripts/sort_projects.sh
10 | say "Install done"
11 |
--------------------------------------------------------------------------------
/ci_scripts/🎯 Pod Update.command:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 | set -euo pipefail
3 | cd "$(dirname "$0")/.."
4 | echo "$PWD"
5 | pod update --verbose || {
6 | say "Update failed"
7 | exit
8 | }
9 | ./ci_scripts/sort_projects.sh
10 | say "Update done"
11 |
--------------------------------------------------------------------------------
/ci_scripts/🎯 安装项目依赖.command:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 | set -euo pipefail
3 | cd "$(dirname "$0")/.."
4 | echo "$PWD"
5 | fastlane setup_project || {
6 | say "Setup failed"
7 | exit
8 | }
9 | say "Setup done"
10 |
--------------------------------------------------------------------------------
/ci_scripts/🎯 打包上传 fir.im.command:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 | set -euo pipefail
3 | cd "$(dirname "$0")/.."
4 | echo "$PWD"
5 | fastlane alpha || {
6 | say "Upload failed"
7 | exit
8 | }
9 | say "Uploaded successfully"
10 |
--------------------------------------------------------------------------------
/ci_scripts/🎯 整理项目文件.command:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 | set -euo pipefail
3 | cd "$(dirname "$0")/.."
4 | ./ci_scripts/sort_projects.sh
5 | say "sort done"
6 |
--------------------------------------------------------------------------------
/fastlane/Appfile:
--------------------------------------------------------------------------------
1 | app_identifier "com.example.app" # The bundle identifier of your app
2 | apple_id "xxx@example.com" # Your Apple email address
3 |
4 | team_id "xxxx" # Developer Portal Team ID
5 |
6 | # you can even provide different app identifiers, Apple IDs and team names per lane:
7 | # More information: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Appfile.md
8 |
--------------------------------------------------------------------------------
/获取项目演示.command:
--------------------------------------------------------------------------------
1 | #! /bin/zsh
2 |
3 | # 拉取 demo 分支
4 | # Copyright © 2020, 2022 BB9z.
5 | # https://github.com/BB9z/iOS-Project-Template
6 |
7 | set -euo pipefail
8 |
9 | cd "$(dirname "$0")"
10 | echo "$PWD"
11 |
12 | logInfo () {
13 | echo "\033[32m$1\033[0m" >&2
14 | }
15 |
16 | logWarning () {
17 | echo "\033[33m$1\033[0m" >&2
18 | }
19 |
20 | logError () {
21 | echo "\033[31m$1\033[0m" >&2
22 | }
23 |
24 | logInput () {
25 | echo "\033[37m$1\033[0m" >&2
26 | }
27 |
28 | CLONE_DESTINATION="Template Demos"
29 | if [ -d "$CLONE_DESTINATION" ]; then
30 | logWarning "Demo 已下载,终止脚本"
31 | exit
32 | fi
33 | git clone --depth 1 --branch=demo https://github.com/BB9z/iOS-Project-Template.git "$CLONE_DESTINATION"
34 | open "$CLONE_DESTINATION"
35 |
--------------------------------------------------------------------------------