├── Flower ├── Flower │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── image.imageset │ │ │ ├── image@2x.png │ │ │ └── Contents.json │ │ ├── image_0.imageset │ │ │ ├── image_0.jpeg │ │ │ └── Contents.json │ │ ├── image_1.imageset │ │ │ ├── image_1.jpeg │ │ │ └── Contents.json │ │ ├── image_2.imageset │ │ │ ├── image_2.jpeg │ │ │ └── Contents.json │ │ ├── bg_image.imageset │ │ │ ├── bg_image@2x.png │ │ │ └── Contents.json │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Tools │ │ ├── AppInfoTool.swift │ │ ├── AppStoreTool.swift │ │ ├── AppTools.swift │ │ ├── AlamofireArrayParameters.swift │ │ ├── DateTool.swift │ │ ├── GCDAsync.swift │ │ ├── ImageDownload.swift │ │ └── FileTool.swift │ ├── Example │ │ ├── ActionSheetVC.swift │ │ ├── TextViewVC.swift │ │ ├── TagsViewVC.swift │ │ ├── EdgeLabelVC.swift │ │ ├── BaseCollectionViewVC.swift │ │ ├── CycleScrollViewVC.xib │ │ ├── FileVC.xib │ │ ├── CycleScrollViewVC.swift │ │ ├── ExpandTableViewVC.swift │ │ ├── FileVC.swift │ │ └── PopupMenuVC.swift │ ├── AppDelegate.swift │ ├── Custom │ │ ├── SGDashedlineView.swift │ │ ├── SGButton.swift │ │ ├── SGEdgeLabel.swift │ │ ├── SGDragView.swift │ │ └── SGBaseCollectionView.swift │ ├── ViewController.swift │ ├── Info.plist │ ├── SceneDelegate.swift │ └── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard ├── Flower.xcodeproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── FlowerTests │ ├── Info.plist │ └── FlowerTests.swift ├── FlowerUITests │ ├── Info.plist │ └── FlowerUITests.swift └── Sources │ ├── SGTextView │ └── SGTextView.swift │ ├── SGCycleScrollView │ └── SGCycleScrollView.swift │ └── SGPopupMenu │ └── SGPopupMenu.swift ├── FlowerObjc ├── FlowerObjc │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── bg_image.imageset │ │ │ ├── bg_image@2x.png │ │ │ └── Contents.json │ │ ├── luckdraw_icon.imageset │ │ │ ├── luckdraw_icon.png │ │ │ ├── luckdraw_icon@2x.png │ │ │ ├── luckdraw_icon@3x.png │ │ │ └── Contents.json │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── ViewController.h │ ├── AppDelegate.h │ ├── SceneDelegate.h │ ├── Example │ │ ├── LabelVC.h │ │ ├── TagsViewVC.h │ │ ├── TextViewVC.h │ │ ├── ItemsViewVC.h │ │ ├── TextViewVC.m │ │ ├── LabelVC.m │ │ ├── ItemsViewVC.m │ │ └── TagsViewVC.m │ ├── main.m │ ├── AppDelegate.m │ ├── Info.plist │ ├── SceneDelegate.m │ ├── ViewController.m │ └── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard ├── FlowerObjc.xcodeproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── SourcesObjc │ ├── Tools │ │ ├── AppTools.h │ │ ├── AppTools.m │ │ ├── HighlightWords │ │ │ ├── SGHighlightWordsTool.h │ │ │ └── SGHighlightWordsTool.m │ │ └── SensitiveWords │ │ │ ├── SGSensitiveWordsTool.h │ │ │ ├── SGSensitiveWordsTool.m │ │ │ └── SensitiveWords.txt │ └── UIKit │ │ ├── SGLabel.h │ │ ├── SGTextView.h │ │ ├── SGLabel.m │ │ ├── SGGuidePageView.h │ │ ├── SGItemsView.h │ │ ├── SGActionSheet.h │ │ ├── SGTextView.m │ │ ├── SGTagsView.h │ │ ├── SGItemsView.m │ │ └── SGGuidePageView.m ├── FlowerObjcTests │ ├── Info.plist │ └── FlowerObjcTests.m └── FlowerObjcUITests │ ├── Info.plist │ └── FlowerObjcUITests.m ├── Flower.xcworkspace ├── xcshareddata │ └── IDEWorkspaceChecks.plist └── contents.xcworkspacedata ├── LICENSE ├── .gitignore └── README.md /Flower/Flower/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Flower/Flower/Assets.xcassets/image.imageset/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingsic/Flower/HEAD/Flower/Flower/Assets.xcassets/image.imageset/image@2x.png -------------------------------------------------------------------------------- /Flower/Flower/Assets.xcassets/image_0.imageset/image_0.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingsic/Flower/HEAD/Flower/Flower/Assets.xcassets/image_0.imageset/image_0.jpeg -------------------------------------------------------------------------------- /Flower/Flower/Assets.xcassets/image_1.imageset/image_1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingsic/Flower/HEAD/Flower/Flower/Assets.xcassets/image_1.imageset/image_1.jpeg -------------------------------------------------------------------------------- /Flower/Flower/Assets.xcassets/image_2.imageset/image_2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingsic/Flower/HEAD/Flower/Flower/Assets.xcassets/image_2.imageset/image_2.jpeg -------------------------------------------------------------------------------- /Flower/Flower/Assets.xcassets/bg_image.imageset/bg_image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingsic/Flower/HEAD/Flower/Flower/Assets.xcassets/bg_image.imageset/bg_image@2x.png -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/Assets.xcassets/bg_image.imageset/bg_image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingsic/Flower/HEAD/FlowerObjc/FlowerObjc/Assets.xcassets/bg_image.imageset/bg_image@2x.png -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/Assets.xcassets/luckdraw_icon.imageset/luckdraw_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingsic/Flower/HEAD/FlowerObjc/FlowerObjc/Assets.xcassets/luckdraw_icon.imageset/luckdraw_icon.png -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/Assets.xcassets/luckdraw_icon.imageset/luckdraw_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingsic/Flower/HEAD/FlowerObjc/FlowerObjc/Assets.xcassets/luckdraw_icon.imageset/luckdraw_icon@2x.png -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/Assets.xcassets/luckdraw_icon.imageset/luckdraw_icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kingsic/Flower/HEAD/FlowerObjc/FlowerObjc/Assets.xcassets/luckdraw_icon.imageset/luckdraw_icon@3x.png -------------------------------------------------------------------------------- /Flower/Flower.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Flower/Flower/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // FlowerObjc 4 | // 5 | // Created by kingsic on 2021/9/7. 6 | // 7 | 8 | #import 9 | 10 | @interface ViewController : UIViewController 11 | 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // FlowerObjc 4 | // 5 | // Created by kingsic on 2021/9/7. 6 | // 7 | 8 | #import 9 | 10 | @interface AppDelegate : UIResponder 11 | 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /Flower.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Flower.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/SceneDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.h 3 | // FlowerObjc 4 | // 5 | // Created by kingsic on 2021/9/7. 6 | // 7 | 8 | #import 9 | 10 | @interface SceneDelegate : UIResponder 11 | 12 | @property (strong, nonatomic) UIWindow * window; 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Flower/Flower.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/Example/LabelVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // LabelVC.h 3 | // SGFastfishExample 4 | // 5 | // Created by kingsic on 2020/12/22. 6 | // Copyright © 2020 kingsic. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface LabelVC : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /FlowerObjc/SourcesObjc/Tools/AppTools.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppTools.h 3 | // AppTools 4 | // 5 | // Created by kingsic on 2021/9/7. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface AppTools : NSObject 13 | 14 | /// 拨打电话号码 15 | + (void)callPhoneNumber:(NSString *)phoneNumber; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/Example/TagsViewVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // TagsViewVC.h 3 | // SGFastfishExample 4 | // 5 | // Created by kingsic on 2020/12/22. 6 | // Copyright © 2020 kingsic. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface TagsViewVC : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/Example/TextViewVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // TextViewVC.h 3 | // SGFastfishExample 4 | // 5 | // Created by kingsic on 2020/12/22. 6 | // Copyright © 2020 kingsic. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface TextViewVC : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/Example/ItemsViewVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // ItemsViewVC.h 3 | // SGFastfishExample 4 | // 5 | // Created by kingsic on 2020/12/22. 6 | // Copyright © 2020 kingsic. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface ItemsViewVC : UIViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /FlowerObjc/SourcesObjc/UIKit/SGLabel.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGLabel.h 3 | // SGLabel 4 | // 5 | // Created by kingsic on 2020/12/19. 6 | // Copyright © 2020 kingsic. All rights reserved. 7 | // 8 | // 内部重写了 drawTextInRect: 方法,实现文字从左上角开始布局 9 | // 10 | 11 | #import 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface SGLabel : UILabel 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /Flower/Flower/Assets.xcassets/image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "image@2x.png", 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 | } 22 | -------------------------------------------------------------------------------- /Flower/Flower/Assets.xcassets/image_0.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "image_0.jpeg", 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 | } 22 | -------------------------------------------------------------------------------- /Flower/Flower/Assets.xcassets/image_1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "image_1.jpeg", 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 | } 22 | -------------------------------------------------------------------------------- /Flower/Flower/Assets.xcassets/image_2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "image_2.jpeg", 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 | } 22 | -------------------------------------------------------------------------------- /Flower/Flower/Assets.xcassets/bg_image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "bg_image@2x.png", 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 | } 22 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/Assets.xcassets/bg_image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "bg_image@2x.png", 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 | } 22 | -------------------------------------------------------------------------------- /FlowerObjc/SourcesObjc/Tools/AppTools.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppTools.m 3 | // AppTools 4 | // 5 | // Created by kingsic on 2021/9/7. 6 | // 7 | 8 | #import "AppTools.h" 9 | 10 | @implementation AppTools 11 | 12 | /// 拨打电话号码 13 | + (void)callPhoneNumber:(NSString *)phoneNumber { 14 | NSURL *tempUrl = [NSURL URLWithString:[NSString stringWithFormat:@"telprompt://%@",phoneNumber]]; 15 | [[UIApplication sharedApplication] openURL:tempUrl options:@{} completionHandler:nil]; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // FlowerObjc 4 | // 5 | // Created by kingsic on 2021/9/7. 6 | // 7 | 8 | #import 9 | #import "AppDelegate.h" 10 | 11 | int main(int argc, char * argv[]) { 12 | NSString * appDelegateClassName; 13 | @autoreleasepool { 14 | // Setup code that might create autoreleased objects goes here. 15 | appDelegateClassName = NSStringFromClass([AppDelegate class]); 16 | } 17 | return UIApplicationMain(argc, argv, nil, appDelegateClassName); 18 | } 19 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/Assets.xcassets/luckdraw_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "luckdraw_icon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "luckdraw_icon@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "luckdraw_icon@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /FlowerObjc/SourcesObjc/UIKit/SGTextView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGTextView.h 3 | // SGTextView 4 | // 5 | // Created by kingsic on 2020/12/11. 6 | // Copyright © 2020 kingsic. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface SGTextView : UITextView 14 | /** 占位文字 */ 15 | @property (nonatomic, copy) NSString *placeholder; 16 | /** 占位文字颜色 */ 17 | @property (nonatomic, strong) UIColor *placeholderColor; 18 | /** 限制输入字数 */ 19 | @property (nonatomic, assign) NSInteger limitNumber; 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /FlowerObjc/SourcesObjc/Tools/HighlightWords/SGHighlightWordsTool.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGHighlightWordsTool.h 3 | // FlowerObjc 4 | // 5 | // Created by kingsic on 2021/11/3. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface SGHighlightWordsTool : NSObject 14 | /** 15 | * 高亮词处理 16 | * 17 | * @param string 字符串 18 | * @param highlightWords 高亮词 19 | * @param highlightWordsColor 高亮词颜色 20 | */ 21 | - (NSMutableAttributedString *)highlightWithString:(NSString *)string highlightWords:(NSString *)highlightWords highlightWordsColor:(UIColor *)highlightWordsColor; 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /FlowerObjc/SourcesObjc/UIKit/SGLabel.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGLabel.m 3 | // SGLabel 4 | // 5 | // Created by kingsic on 2020/12/19. 6 | // Copyright © 2020 kingsic. All rights reserved. 7 | // 8 | 9 | #import "SGLabel.h" 10 | 11 | @implementation SGLabel 12 | 13 | - (void)drawTextInRect:(CGRect)rect { 14 | CGRect tempRect = [self textRectForBounds:rect limitedToNumberOfLines:self.numberOfLines]; 15 | [super drawTextInRect:tempRect]; 16 | } 17 | 18 | - (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines { 19 | CGRect textRect = [super textRectForBounds:bounds limitedToNumberOfLines:numberOfLines]; 20 | textRect.origin.y = bounds.origin.y; 21 | return textRect; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Flower/FlowerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Flower/FlowerUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjcTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjcUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /FlowerObjc/SourcesObjc/Tools/SensitiveWords/SGSensitiveWordsTool.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGSensitiveWordsTool 3 | // 4 | // 此工具类用于对文本中的敏感词进行处理,以及判断文本中是否含有敏感词 5 | // 6 | // Created by kingsic on 2017/3/16. 7 | // Copyright © 2017年 kingsic. All rights reserved. 8 | // 9 | 10 | #import 11 | 12 | @interface SGSensitiveWordsTool : NSObject 13 | /* 通过单例的方式创建对象 */ 14 | + (instancetype)shared; 15 | 16 | /** 17 | * 加载本地的敏感词库 18 | * 19 | * @param filepath 敏感词文件的路径 20 | */ 21 | - (void)initFilter:(NSString *)filepath; 22 | 23 | /** 24 | * 判断文本中是否含有敏感词 25 | * 26 | * @param string 文本字符串 27 | * 28 | * @return 是否含有敏感词 29 | */ 30 | - (BOOL)includeSensitiveWords:(NSString *)string; 31 | 32 | /** 33 | * 将文本中含有的敏感词进行替换 34 | * 35 | * @param string 文本字符串 36 | * @param tempWord 替换的字符 37 | * 38 | * @return 过滤完敏感词之后的文本 39 | */ 40 | - (NSString *)replaceSensitiveWords:(NSString *)string withWord:(NSString *)tempWord; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjcTests/FlowerObjcTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlowerObjcTests.m 3 | // FlowerObjcTests 4 | // 5 | // Created by kingsic on 2021/9/7. 6 | // 7 | 8 | #import 9 | 10 | @interface FlowerObjcTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation FlowerObjcTests 15 | 16 | - (void)setUp { 17 | // Put setup code here. This method is called before the invocation of each test method in the class. 18 | } 19 | 20 | - (void)tearDown { 21 | // Put teardown code here. This method is called after the invocation of each test method in the class. 22 | } 23 | 24 | - (void)testExample { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | - (void)testPerformanceExample { 30 | // This is an example of a performance test case. 31 | [self measureBlock:^{ 32 | // Put the code you want to measure the time of here. 33 | }]; 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /Flower/FlowerTests/FlowerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlowerTests.swift 3 | // FlowerTests 4 | // 5 | // Created by kingsic on 2021/9/7. 6 | // 7 | 8 | import XCTest 9 | @testable import Flower 10 | 11 | class FlowerTests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDownWithError() throws { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() throws { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | } 25 | 26 | func testPerformanceExample() throws { 27 | // This is an example of a performance test case. 28 | self.measure { 29 | // Put the code you want to measure the time of here. 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 kingsic 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 | -------------------------------------------------------------------------------- /Flower/Flower/Tools/AppInfoTool.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppInfoTool.swift 3 | // Flower 4 | // 5 | // Created by kingsic on 2021/11/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class AppInfoTool: NSObject { 11 | /// Get the app name 12 | /// 13 | /// - returns: App name 14 | public class func appName() -> String { 15 | var appName = Bundle.main.infoDictionary?["CFBundleName"] 16 | if appName == nil { 17 | appName = Bundle.main.infoDictionary?["CFBundleDisplayName"] 18 | } 19 | return appName as! String 20 | } 21 | 22 | /// Get the app version number 23 | /// 24 | /// - returns: App version number 25 | public class func appVersion() -> String { 26 | let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] 27 | return appVersion as! String 28 | } 29 | 30 | /// Get the app build version number 31 | /// 32 | /// - returns: App build version number 33 | public class func appBuildVersion() -> String { 34 | let appVersion = Bundle.main.infoDictionary?["CFBundleVersion"] 35 | return appVersion as! String 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /FlowerObjc/SourcesObjc/Tools/HighlightWords/SGHighlightWordsTool.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGHighlightWordsTool.m 3 | // FlowerObjc 4 | // 5 | // Created by kingsic on 2021/11/3. 6 | // 7 | 8 | #import "SGHighlightWordsTool.h" 9 | 10 | @implementation SGHighlightWordsTool 11 | - (NSMutableAttributedString *)highlightWithString:(NSString *)string highlightWords:(NSString *)highlightWords highlightWordsColor:(UIColor *)highlightWordsColor { 12 | NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string]; 13 | NSString *copyTotalString = string; 14 | NSMutableString *replaceString = [NSMutableString stringWithCapacity:0]; 15 | for (int i = 0; i < highlightWords.length; i ++) { 16 | [replaceString appendString:@" "]; 17 | } 18 | while ([copyTotalString rangeOfString:highlightWords].location != NSNotFound) { 19 | NSRange range = [copyTotalString rangeOfString:highlightWords]; 20 | [attributedString addAttribute:NSForegroundColorAttributeName value:highlightWordsColor range:range]; 21 | copyTotalString = [copyTotalString stringByReplacingCharactersInRange:range withString:replaceString]; 22 | } 23 | return attributedString; 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Flower/Flower/Example/ActionSheetVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActionSheet.swift 3 | // Flower 4 | // 5 | // Created by kingsic on 2022/2/6. 6 | // 7 | 8 | import UIKit 9 | 10 | class ActionSheetVC: UIViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | view.backgroundColor = .green 15 | 16 | } 17 | 18 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 19 | let configure = SGActionSheetConfigure() 20 | configure.message = "您确定要退出吗?" 21 | let ASheet = SGActionSheet(titles: ["确定", "再想想"], configure: configure) 22 | ASheet.titlesClickBlock { actionSheet, index in 23 | print("index - - \(index)") 24 | } 25 | ASheet.setTitle(color: .red, index: 0) 26 | actionSheet(ASheet) 27 | } 28 | 29 | 30 | /* 31 | // MARK: - Navigation 32 | 33 | // In a storyboard-based application, you will often want to do a little preparation before navigation 34 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 35 | // Get the new view controller using segue.destination. 36 | // Pass the selected object to the new view controller. 37 | } 38 | */ 39 | 40 | } 41 | -------------------------------------------------------------------------------- /Flower/Flower/Example/TextViewVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextViewVC.swift 3 | // Flower 4 | // 5 | // Created by kingsic on 2021/9/8. 6 | // 7 | 8 | import UIKit 9 | 10 | class TextViewVC: UIViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | view.backgroundColor = .white 16 | navigationItem.title = "SGTextView" 17 | // Do any additional setup after loading the view. 18 | 19 | let textView = SGTextView() 20 | textView.frame = CGRect.init(x: 20, y: navBarHeight + 20, width: screenWidth - 40, height: 200) 21 | textView.backgroundColor = .green 22 | textView.placeHolder = "请输入你想要表达的内容" 23 | textView.placeHolderColor = .red 24 | textView.limitNumber = 7 25 | view.addSubview(textView) 26 | } 27 | 28 | 29 | /* 30 | // MARK: - Navigation 31 | 32 | // In a storyboard-based application, you will often want to do a little preparation before navigation 33 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 34 | // Get the new view controller using segue.destination. 35 | // Pass the selected object to the new view controller. 36 | } 37 | */ 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Flower/Flower/Tools/AppStoreTool.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppStoreTool.swift 3 | // Flower 4 | // 5 | // Created by kingsic on 2021/11/23. 6 | // 7 | 8 | import UIKit 9 | 10 | class AppStoreTool: NSObject { 11 | /// Jump to app store 12 | /// 13 | /// - parameter appID: App ID 14 | public class func jumptoAppStore(appID: String) { 15 | let string = "https://itunes.apple.com/app/apple-store/id\(appID)?mt=8" 16 | guard let url = URL.init(string: string) else { return } 17 | UIApplication.shared.open(url, options: Dictionary.init()) { (success: Bool) in 18 | 19 | } 20 | } 21 | public typealias WriteReviewCompletionBlock = ((Bool) -> ())? 22 | /// Jump to app store to write a review 23 | /// 24 | /// - parameter appID: App ID 25 | public class func jumptoAppStoreWriteReview(appID: String, completionBlock: WriteReviewCompletionBlock) { 26 | let string = "itms-apps://itunes.apple.com/app/id\(appID)?action=write-review" 27 | guard let url = URL.init(string: string) else { return } 28 | UIApplication.shared.open(url, options: Dictionary.init()) { (success: Bool) in 29 | if completionBlock != nil { 30 | completionBlock!(success) 31 | } 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /FlowerObjc/SourcesObjc/UIKit/SGGuidePageView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGGuidePageView.h 3 | // SGGuidePageView 4 | // 5 | // Created by kingsic on 2017/8/7. 6 | // Copyright © 2017 kingsic. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef enum : NSUInteger { 12 | /// 不存在,默认 13 | SGPageControlTypeNone, 14 | /// 一直存在 15 | SGPageControlTypeAlways, 16 | /// 存在,但最后一个页面会消失 17 | SGPageControlTypeDisappear, 18 | } SGPageControlType; 19 | 20 | @interface SGGuidePageView : UIView 21 | /** 引导页数据 */ 22 | @property (nonatomic, strong) NSArray *images; 23 | /** SGPageControl 类型 */ 24 | @property (nonatomic, assign) SGPageControlType pageControlType; 25 | /** PageControl 相对父视图的偏移量,默认为 0.87 */ 26 | @property (nonatomic, assign) CGFloat pageControlOffsetY; 27 | /** SGPageControlTypeNone 类型外,pageControl 默认时颜色 */ 28 | @property (nonatomic, strong) UIColor *pageIndicatorTintColor; 29 | /** SGPageControlTypeNone 类型外,pageControl 选中时颜色 */ 30 | @property (nonatomic, strong) UIColor *currentPageIndicatorTintColor; 31 | /** 进入按钮 */ 32 | @property (nonatomic, strong) UIButton *enterBtn; 33 | /** SGGuidePageView 消失时间,默认为 0.25s */ 34 | @property (nonatomic, assign) CGFloat durationTime; 35 | /** 最后一页往左滑时是否消失,默认为 NO */ 36 | @property (nonatomic, assign) BOOL lastPageDisapper; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/Example/TextViewVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // TextViewVC.m 3 | // SGFastfishExample 4 | // 5 | // Created by kingsic on 2020/12/22. 6 | // Copyright © 2020 kingsic. All rights reserved. 7 | // 8 | 9 | #import "TextViewVC.h" 10 | #import "SGTextView.h" 11 | 12 | @interface TextViewVC () 13 | 14 | @end 15 | 16 | @implementation TextViewVC 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | // Do any additional setup after loading the view. 21 | self.navigationItem.title = @"SGTextView"; 22 | self.view.backgroundColor = [UIColor whiteColor]; 23 | 24 | SGTextView *tv = [[SGTextView alloc] init]; 25 | tv.frame = CGRectMake(50, 100, self.view.frame.size.width - 100, 100); 26 | tv.backgroundColor = [UIColor greenColor]; 27 | tv.layer.cornerRadius = 10; 28 | tv.layer.borderWidth = 1; 29 | tv.layer.borderColor = [UIColor blackColor].CGColor; 30 | tv.placeholder = @"分享美好事物……"; 31 | tv.placeholderColor = [UIColor redColor]; 32 | [self.view addSubview:tv]; 33 | } 34 | 35 | /* 36 | #pragma mark - Navigation 37 | 38 | // In a storyboard-based application, you will often want to do a little preparation before navigation 39 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 40 | // Get the new view controller using [segue destinationViewController]. 41 | // Pass the selected object to the new view controller. 42 | } 43 | */ 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // FlowerObjc 4 | // 5 | // Created by kingsic on 2021/9/7. 6 | // 7 | 8 | #import "AppDelegate.h" 9 | 10 | @interface AppDelegate () 11 | 12 | @end 13 | 14 | @implementation AppDelegate 15 | 16 | 17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 18 | // Override point for customization after application launch. 19 | return YES; 20 | } 21 | 22 | 23 | #pragma mark - UISceneSession lifecycle 24 | 25 | 26 | - (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { 27 | // Called when a new scene session is being created. 28 | // Use this method to select a configuration to create the new scene with. 29 | return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; 30 | } 31 | 32 | 33 | - (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions { 34 | // Called when the user discards a scene session. 35 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 36 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 37 | } 38 | 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Flower/Flower/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Flower 4 | // 5 | // Created by kingsic on 2021/9/7. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/Example/LabelVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // LabelVC.m 3 | // SGFastfishExample 4 | // 5 | // Created by kingsic on 2020/12/22. 6 | // Copyright © 2020 kingsic. All rights reserved. 7 | // 8 | 9 | #import "LabelVC.h" 10 | #import "SGLabel.h" 11 | #import "SGHighlightWordsTool.h" 12 | 13 | @interface LabelVC () 14 | 15 | @end 16 | 17 | @implementation LabelVC 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | // Do any additional setup after loading the view. 22 | self.navigationItem.title = @"SGLabel"; 23 | self.view.backgroundColor = [UIColor whiteColor]; 24 | 25 | NSString *tempStr = @"曾经沧海难为水,除却巫山不是云\n取次花丛懒回顾,半缘修道半缘君\n\n这是高亮词"; 26 | SGLabel *lab = [[SGLabel alloc] init]; 27 | lab.frame = CGRectMake(50, 100, self.view.frame.size.width - 100, 100); 28 | lab.backgroundColor = [UIColor greenColor]; 29 | lab.numberOfLines = 0; 30 | lab.text = tempStr; 31 | lab.attributedText = [[[SGHighlightWordsTool alloc] init] highlightWithString:tempStr highlightWords:@"这是高亮词" highlightWordsColor:[UIColor redColor]]; 32 | [self.view addSubview:lab]; 33 | } 34 | 35 | /* 36 | #pragma mark - Navigation 37 | 38 | // In a storyboard-based application, you will often want to do a little preparation before navigation 39 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 40 | // Get the new view controller using [segue destinationViewController]. 41 | // Pass the selected object to the new view controller. 42 | } 43 | */ 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Flower/Flower/Example/TagsViewVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TagsViewVC.swift 3 | // Flower 4 | // 5 | // Created by kingsic on 2021/9/8. 6 | // 7 | 8 | import UIKit 9 | 10 | class TagsViewVC: UIViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | view.backgroundColor = .white 16 | navigationItem.title = "SGTagsView" 17 | // Do any additional setup after loading the view. 18 | 19 | let tagsViewConfigure = SGTagsViewConfigure() 20 | tagsViewConfigure.selectedColor = .white 21 | tagsViewConfigure.selectedBackgroundColor = .black 22 | tagsViewConfigure.cornerRadius = 10 23 | tagsViewConfigure.style = .vertical 24 | 25 | let tagsView = SGTagsView.init(frame: CGRect.init(x: 20, y: navBarHeight + 20, width: screenWidth - 40, height: 200), configure: tagsViewConfigure) 26 | tagsView.tags = ["Objective-C", "Swift", "Flutter", "Java", "Kotlin"] 27 | tagsView.backgroundColor = .green 28 | tagsView.isFixedHeight = true 29 | view.addSubview(tagsView) 30 | } 31 | 32 | 33 | /* 34 | // MARK: - Navigation 35 | 36 | // In a storyboard-based application, you will often want to do a little preparation before navigation 37 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 38 | // Get the new view controller using segue.destination. 39 | // Pass the selected object to the new view controller. 40 | } 41 | */ 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Flower/FlowerUITests/FlowerUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlowerUITests.swift 3 | // FlowerUITests 4 | // 5 | // Created by kingsic on 2021/9/7. 6 | // 7 | 8 | import XCTest 9 | 10 | class FlowerUITests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 19 | } 20 | 21 | override func tearDownWithError() throws { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testExample() throws { 26 | // UI tests must launch the application that they test. 27 | let app = XCUIApplication() 28 | app.launch() 29 | 30 | // Use recording to get started writing UI tests. 31 | // Use XCTAssert and related functions to verify your tests produce the correct results. 32 | } 33 | 34 | func testLaunchPerformance() throws { 35 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { 36 | // This measures how long it takes to launch your application. 37 | measure(metrics: [XCTApplicationLaunchMetric()]) { 38 | XCUIApplication().launch() 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjcUITests/FlowerObjcUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // FlowerObjcUITests.m 3 | // FlowerObjcUITests 4 | // 5 | // Created by kingsic on 2021/9/7. 6 | // 7 | 8 | #import 9 | 10 | @interface FlowerObjcUITests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation FlowerObjcUITests 15 | 16 | - (void)setUp { 17 | // Put setup code here. This method is called before the invocation of each test method in the class. 18 | 19 | // In UI tests it is usually best to stop immediately when a failure occurs. 20 | self.continueAfterFailure = NO; 21 | 22 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 23 | } 24 | 25 | - (void)tearDown { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | } 28 | 29 | - (void)testExample { 30 | // UI tests must launch the application that they test. 31 | XCUIApplication *app = [[XCUIApplication alloc] init]; 32 | [app launch]; 33 | 34 | // Use recording to get started writing UI tests. 35 | // Use XCTAssert and related functions to verify your tests produce the correct results. 36 | } 37 | 38 | - (void)testLaunchPerformance { 39 | if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *)) { 40 | // This measures how long it takes to launch your application. 41 | [self measureWithMetrics:@[[[XCTApplicationLaunchMetric alloc] init]] block:^{ 42 | [[[XCUIApplication alloc] init] launch]; 43 | }]; 44 | } 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /Flower/Flower/Custom/SGDashedlineView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SGDashedlineView.swift 3 | // SGDashedlineView 4 | // 5 | // Created by kingsic on 2022/6/30. 6 | // 7 | 8 | import UIKit 9 | 10 | enum Direction { 11 | case horizontal, vertical 12 | } 13 | 14 | class SGDashedlineView: UIView { 15 | 16 | // 虚线的方向,默认为水平 17 | var direction: Direction = .horizontal 18 | 19 | // 虚线的颜色,默认为红色 20 | var color: UIColor = .red 21 | 22 | // 虚线的宽度,默认为1.0 23 | var width: CGFloat = 1.0 24 | 25 | // 虚线的长度,默认为5.0 26 | var length: CGFloat = 5.0 27 | 28 | // 虚线的间距,默认为5.0 29 | var spacing: CGFloat = 5.0 30 | 31 | 32 | 33 | override func draw(_ rect: CGRect) { 34 | super.draw(rect) 35 | 36 | let context = UIGraphicsGetCurrentContext() 37 | context?.setStrokeColor(color.cgColor) 38 | context?.setLineWidth(width) 39 | 40 | if direction == .horizontal { 41 | let offsetY = 0.5 * frame.size.height 42 | let width = frame.size.width 43 | context?.move(to: CGPoint(x: 0, y: offsetY)) 44 | context?.addLine(to: CGPoint(x: width, y: offsetY)) 45 | } else { 46 | let offsetX = 0.5 * frame.size.width 47 | let height = frame.size.height 48 | context?.move(to: CGPoint(x: offsetX, y: 0)) 49 | context?.addLine(to: CGPoint(x: offsetX, y: height)) 50 | } 51 | 52 | // 虚线的长和间距 53 | let arr: [CGFloat] = [length, spacing] 54 | context?.setLineDash(phase: 0, lengths: arr) 55 | context?.drawPath(using: .stroke) 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /Flower/Flower/Custom/SGButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SGButton.swift 3 | // SGButton 4 | // 5 | // Created by kingsic on 2021/11/23. 6 | // Copyright © 2020 kingsic. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class UnHighlightedButton: UIButton { 12 | public override var isHighlighted: Bool { 13 | set {} 14 | get {return false} 15 | } 16 | } 17 | 18 | public class SpacingButton: UIButton { 19 | /// 文字距离左右边侧的距离,默认为 0 20 | /// 21 | /// 如果右侧存在其他控件,使用 SnapKit 进行约束时,记得加上2倍的 spacing 宽度 22 | /// 23 | /// 原因是:SnapKit 约束的调用时机要比 layoutSubviews 早 24 | public var spacing: CGFloat = 0 25 | 26 | public override func layoutSubviews() { 27 | super.layoutSubviews() 28 | 29 | if let tempText = titleLabel?.text { 30 | guard spacing > 0 else { 31 | return 32 | } 33 | 34 | let width = tempText.calculateStringWidth(font: (titleLabel?.font)!) 35 | frame.size.width = width + 2 * spacing 36 | } 37 | } 38 | } 39 | 40 | fileprivate extension String { 41 | /// Calculate the width of the string according to the font size 42 | /// 43 | /// - parameter font: UIFont 44 | /// 45 | /// - returns: The width of the calculated string 46 | func calculateStringWidth(font: UIFont) -> CGFloat { 47 | let attrs = [NSAttributedString.Key.font: font] 48 | let tempRect = (self as NSString).boundingRect(with: CGSize(width: 0, height: 0), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: attrs, context: nil) 49 | return tempRect.size.width 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Flower/Flower/Tools/AppTools.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppTools.swift 3 | // AppTools 4 | // 5 | // Created by kingsic on 2020/8/19. 6 | // Copyright © 2020 kingsic. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public let screenWidth = UIScreen.main.bounds.size.width 12 | public let screenHeight = UIScreen.main.bounds.size.height 13 | public let statusBarHeight = UIApplication.shared.windows.first?.windowScene?.statusBarManager?.statusBarFrame.size.height 14 | public let navBarHeight = statusBarHeight! + 44 15 | public let tabBarHeight = statusBarHeight! == 20 ? 49 : 83 16 | /// 安全边界到底部的距离 17 | public let safeAreaInsetsBottom = statusBarHeight == 20 ? 0 : 34; 18 | 19 | 20 | public class AppTools: NSObject { 21 | /// After processing the hidden navigation bar, the scrollView offsets the status bar height downward 22 | /// 23 | /// - parameter scrollView: ScrollView 24 | /// 25 | /// - parameter controller: Controller of Scrollview 26 | public class func adjust(scrollView: UIScrollView, controller: UIViewController) { 27 | if #available(iOS 11.0, *) { 28 | scrollView.contentInsetAdjustmentBehavior = .never 29 | } else { 30 | controller.automaticallyAdjustsScrollViewInsets = false 31 | } 32 | } 33 | 34 | /// Make a call 35 | /// 36 | /// - parameter number: Telephone number 37 | public class func callPhone(number: String) { 38 | let phoneNum = "telprompt://\(number)" 39 | guard let url = URL.init(string: phoneNum) else { return } 40 | UIApplication.shared.open(url, options: Dictionary(), completionHandler: nil) 41 | } 42 | 43 | } 44 | 45 | -------------------------------------------------------------------------------- /Flower/Flower/Example/EdgeLabelVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LabelVC.swift 3 | // Flower 4 | // 5 | // Created by kingsic on 2021/9/8. 6 | // 7 | 8 | import UIKit 9 | 10 | class EdgeLabelVC: UIViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | view.backgroundColor = .white 16 | navigationItem.title = "SGEdgeLabel" 17 | // Do any additional setup after loading the view. 18 | 19 | let tempStr = "曾经沧海难为水,除却巫山不是云,取次花丛懒回顾,半缘修道半缘君\n曾经沧海难为水,除却巫山不是云,取次花丛懒回顾,半缘修道半缘君" 20 | 21 | let label = SGEdgeLabel.init() 22 | label.frame = CGRect.init(x: 20, y: navBarHeight + 20, width: screenWidth - 40, height: 130) 23 | label.backgroundColor = .green 24 | label.numberOfLines = 0 25 | label.contentInset = SGEdgeInsets(top: 10, left: 10, right: 10) 26 | label.text = tempStr 27 | view.addSubview(label) 28 | 29 | let lab = UILabel.init() 30 | lab.frame = CGRect.init(x: 20, y: label.frame.maxY + 20, width: screenWidth - 40, height: 130) 31 | lab.backgroundColor = .green 32 | lab.numberOfLines = 0 33 | lab.text = tempStr 34 | view.addSubview(lab) 35 | 36 | } 37 | 38 | 39 | /* 40 | // MARK: - Navigation 41 | 42 | // In a storyboard-based application, you will often want to do a little preparation before navigation 43 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 44 | // Get the new view controller using segue.destination. 45 | // Pass the selected object to the new view controller. 46 | } 47 | */ 48 | 49 | } 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 52 | 53 | fastlane/report.xml 54 | fastlane/Preview.html 55 | fastlane/screenshots/**/*.png 56 | fastlane/test_output 57 | 58 | # Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | -------------------------------------------------------------------------------- /Flower/Flower/Custom/SGEdgeLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SGLabel.swift 3 | // SGLabel 4 | // 5 | // Created by kingsic on 2020/12/20. 6 | // Copyright © 2020 kingsic. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public struct SGEdgeInsets { 12 | public var top: CGFloat = 10.0 13 | public var left: CGFloat = 0.0 14 | public var right: CGFloat = 0.0 15 | } 16 | 17 | public extension SGEdgeInsets { 18 | static let zero: SGEdgeInsets = SGEdgeInsets() 19 | } 20 | 21 | public class SGEdgeLabel: UILabel { 22 | /// 文本内边距 23 | public var contentInset: SGEdgeInsets = .zero 24 | 25 | /// 文本是否自适应,默认为:false 26 | public var autosized: Bool = false 27 | 28 | public override func drawText(in rect: CGRect) { 29 | let tempRect = self.textRect(forBounds: rect, limitedToNumberOfLines: self.numberOfLines) 30 | super.drawText(in: tempRect) 31 | } 32 | 33 | public override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect { 34 | var rect: CGRect = super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines) 35 | let offsetTop = contentInset.top < 0 ? 0 : contentInset.top 36 | let offsetLeft = contentInset.left < 0 ? 0 : contentInset.left 37 | let offsetRight = contentInset.right < 0 ? 0 : contentInset.right 38 | 39 | rect.origin.x = offsetLeft 40 | rect.origin.y = offsetTop 41 | 42 | if autosized { 43 | rect.size.width += offsetLeft + offsetRight 44 | } else { 45 | rect.size.width -= offsetLeft + offsetRight 46 | } 47 | 48 | return rect 49 | } 50 | 51 | 52 | } 53 | -------------------------------------------------------------------------------- /FlowerObjc/SourcesObjc/UIKit/SGItemsView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGItemsView.h 3 | // SGItemsView 4 | // 5 | // Created by kingsic on 2020/10/15. 6 | // Copyright © 2020 kingsic. All rights reserved. 7 | // 8 | // 这里需要说明一点的是:SGItemsView 内部的 titleLabel 的高占据 itemSize 高的 1/3,且距离底部边距为 0;而内部的 imageView 的高为 itemSize 高的(2/3 - 5),这里的 5 指的是 imageView 距离顶部的距离 9 | // 10 | 11 | #import 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | /** 根据下标配置 UIImageView 的回调 Block */ 16 | typedef void(^SGItemsViewConfigureImgViewBlock)(UIImageView *imageView, NSInteger index); 17 | /** 点击 item 的回调 Block */ 18 | typedef void(^SGItemsViewItemClickBlock)(NSInteger index); 19 | 20 | @interface SGItemsView : UIView 21 | /** Item 标题数组 */ 22 | @property (nonatomic, strong) NSArray *titles; 23 | /** Item 标题字号大小,默认 [UIFont systemFontOfSize:12] */ 24 | @property (nonatomic, strong) UIFont *titleFont; 25 | /** Item 标题颜色,默认为黑色 */ 26 | @property (nonatomic, strong) UIColor *titleColor; 27 | /** 配置 imageView 回调函数,将 imageView 留给外部处理:如网络或本地 image 的加载图像 */ 28 | @property (nonatomic, copy) SGItemsViewConfigureImgViewBlock configureImgViewBlock; 29 | /** Item 大小,设置高度时,请考虑 contentinset 属性的顶部和底部值 */ 30 | @property (nonatomic, assign) CGSize itemSize; 31 | /** Item 的内边距 */ 32 | @property (nonatomic, assign) UIEdgeInsets contentInset; 33 | /** 是否支持分页滚动,默认为 NO */ 34 | @property (nonatomic, assign) BOOL pagingEnabled; 35 | /** 是否支持水平滚动,默认为 NO */ 36 | @property (nonatomic, assign) BOOL scrollDirectionHorizontal; 37 | /** 是否显示水平滚动条,默认为 NO */ 38 | @property (nonatomic, assign) BOOL showsHorizontalScrollIndicator; 39 | /** Item 点击回调 block */ 40 | @property (nonatomic, copy) SGItemsViewItemClickBlock itemClickBlock; 41 | /** 根据对应的下标设置标题颜色 */ 42 | - (void)setItemTitleColor:(UIColor *)titleColor forIndex:(NSInteger)index; 43 | 44 | @end 45 | 46 | NS_ASSUME_NONNULL_END 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flower 2 | 3 | 4 | * 本框架中的 SGTagsView 设计来源于 [SGPagingView](https://github.com/kingsic/SGPagingView) 框架中的 SGPageTitleView 5 | 6 | 7 | ## Flower 8 | |视图|注释| 9 | |----|-----| 10 | |SGActionSheet|弹窗 11 | |SGCycleScrollView|文字、图片轮播 12 | |SGPopupMenu|弹框菜单 13 | |SGItemsView|item视图 14 | |SGTagsView|标签视图 15 | |SGTextView|placeholder、placeholderColor、limitNumber 16 | 17 | 18 | ## FlowerObjc 19 | |视图|注释| 20 | |----|-----| 21 | |SGLabel|文字从左上方开始布局 22 | |SGTextView|placeholder、placeholderColor、limitNumber 23 | |SGTagsView|标签视图 24 | |SGItemsView|item视图 25 | |SGActionSheet|底部弹窗视图(微信、微博样式) 26 | |SGGuidePageView|引导页 27 | 28 | 29 | ## 代码介绍(详细使用,请参考 API) 30 | #### SGTagsView 的使用 31 | ``` 32 | SGTagsViewConfigure *configure = [SGTagsViewConfigure configure]; 33 | 34 | NSArray *tags = @[@"iPhone 8", @"iPhone 8P", @"iPhone X", @"iPhone XR", @"iPhone XS", @"iPhone XS Max"]; 35 | SGTagsView *tagsView = [SGTagsView tagsViewWithFrame:CGRectMake(0, 100, self.view.frame.size.width, 50) configure:configure]; 36 | tagsView.tags = tags; 37 | [self.view addSubview:tagsView]; 38 | tagsView.singleSelectBlock = ^(SGTagsView * _Nonnull tagsView, NSString *tag, NSInteger index) { 39 | NSLog(@"%@ - - %ld", tag, index); 40 | }; 41 | ``` 42 | 43 | #### SGActionSheet 的使用 44 | ``` 45 | SGActionSheetConfigure *asc = [SGActionSheetConfigure configure]; 46 | SGActionSheet *as = [[SGActionSheet alloc] initWithOtherTitles:@[@"确定"] configure:asc]; 47 | as.otherTitleClickBlock = ^(NSInteger index) { 48 | NSLog(@"index - - %ld", index); 49 | }; 50 | [as actionSheet]; 51 | ``` 52 | 53 | 54 | ### QQ音乐、美团、天猫以及SGTagsView标签效果图展示
55 | ![](https://github.com/kingsic/Useless/blob/master/SGRichView/SGTagsView_QQ.png) ![](https://github.com/kingsic/Useless/blob/master/SGRichView/SGTagsView_MT.png) 56 | 57 | ![](https://github.com/kingsic/Useless/blob/master/SGRichView/SGTagsView_Tmall.png) ![](https://github.com/kingsic/Useless/blob/master/SGRichView/SGTagsView_mine.png) 58 | -------------------------------------------------------------------------------- /Flower/Flower/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Flower/Flower/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Flower 4 | // 5 | // Created by kingsic on 2021/9/7. 6 | // 7 | 8 | import UIKit 9 | 10 | class ViewController: UIViewController { 11 | 12 | @IBOutlet weak var tableView: UITableView! 13 | 14 | let dataSource = [ 15 | ["SGActionSheet": ActionSheetVC.self], 16 | ["SGCycleScrollView": CycleScrollViewVC.self], 17 | ["SGEdgeLabel": EdgeLabelVC.self], 18 | ["ExpandTableView": ExpandTableViewVC.self], 19 | ["SGBaseCollectionView": BaseCollectionViewVC.self], 20 | ["SGPopupMenu": PopupMenuVC.self], 21 | ["SGTagsView": TagsViewVC.self], 22 | ["SGTextView": TextViewVC.self], 23 | ["文件数据储存相关": FileVC.self] 24 | ] 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | // Do any additional setup after loading the view. 29 | 30 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cellID") 31 | tableView.tableFooterView = UIView() 32 | } 33 | 34 | } 35 | 36 | extension ViewController: UITableViewDelegate { 37 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 38 | let vc = dataSource[indexPath.row].values.first 39 | if let tempVC = vc { 40 | navigationController?.pushViewController(tempVC.init(), animated: true) 41 | } 42 | } 43 | } 44 | 45 | extension ViewController: UITableViewDataSource { 46 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 47 | return dataSource.count 48 | } 49 | 50 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 51 | let cell = tableView.dequeueReusableCell(withIdentifier: "cellID", for: indexPath) 52 | cell.accessoryType = .disclosureIndicator 53 | cell.selectionStyle = .none 54 | cell.backgroundColor = .clear 55 | cell.textLabel?.text = dataSource[indexPath.row].keys.first 56 | return cell 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /Flower/Flower/Tools/AlamofireArrayParameters.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlamofireArrayParameters.swift 3 | // AlamofireArrayParameters 4 | // 5 | // Created by kingsic on 2022/1/20. 6 | // Copyright © 2022 kingsic. All rights reserved. 7 | // 8 | 9 | /* 10 | import Alamofire 11 | 12 | private let arrayParametersKey = "arrayParametersKey" 13 | 14 | /// Extenstion that allows an array be sent as a request parameters 15 | extension Array { 16 | /// Convert the receiver array to a `Parameters` object. 17 | func asParameters() -> Parameters { 18 | return [arrayParametersKey: self] 19 | } 20 | } 21 | 22 | /// Convert the parameters into a json array, and it is added as the request body. 23 | /// The array must be sent as parameters using its `asParameters` method. 24 | public struct ArrayEncoding: ParameterEncoding { 25 | 26 | /// The options for writing the parameters as JSON data. 27 | public let options: JSONSerialization.WritingOptions 28 | 29 | /// Creates a new instance of the encoding using the given options 30 | /// 31 | /// - parameter options: The options used to encode the json. Default is `[]` 32 | /// 33 | /// - returns: The new instance 34 | public init(options: JSONSerialization.WritingOptions = []) { 35 | self.options = options 36 | } 37 | 38 | public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { 39 | var urlRequest = try urlRequest.asURLRequest() 40 | 41 | guard let parameters = parameters, let array = parameters[arrayParametersKey] else { 42 | return urlRequest 43 | } 44 | 45 | do { 46 | let data = try JSONSerialization.data(withJSONObject: array, options: options) 47 | if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { 48 | urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") 49 | } 50 | urlRequest.httpBody = data 51 | } catch { 52 | throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error)) 53 | } 54 | 55 | return urlRequest 56 | } 57 | } 58 | 59 | */ 60 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UIApplicationSupportsIndirectInputEvents 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | Main 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /Flower/Flower/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UIApplicationSupportsIndirectInputEvents 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | Main 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/SceneDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.m 3 | // FlowerObjc 4 | // 5 | // Created by kingsic on 2021/9/7. 6 | // 7 | 8 | #import "SceneDelegate.h" 9 | 10 | @interface SceneDelegate () 11 | 12 | @end 13 | 14 | @implementation SceneDelegate 15 | 16 | 17 | - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | } 22 | 23 | 24 | - (void)sceneDidDisconnect:(UIScene *)scene { 25 | // Called as the scene is being released by the system. 26 | // This occurs shortly after the scene enters the background, or when its session is discarded. 27 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 28 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 29 | } 30 | 31 | 32 | - (void)sceneDidBecomeActive:(UIScene *)scene { 33 | // Called when the scene has moved from an inactive state to an active state. 34 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 35 | } 36 | 37 | 38 | - (void)sceneWillResignActive:(UIScene *)scene { 39 | // Called when the scene will move from an active state to an inactive state. 40 | // This may occur due to temporary interruptions (ex. an incoming phone call). 41 | } 42 | 43 | 44 | - (void)sceneWillEnterForeground:(UIScene *)scene { 45 | // Called as the scene transitions from the background to the foreground. 46 | // Use this method to undo the changes made on entering the background. 47 | } 48 | 49 | 50 | - (void)sceneDidEnterBackground:(UIScene *)scene { 51 | // Called as the scene transitions from the foreground to the background. 52 | // Use this method to save data, release shared resources, and store enough scene-specific state information 53 | // to restore the scene back to its current state. 54 | } 55 | 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /Flower/Flower/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Flower 4 | // 5 | // Created by kingsic on 2021/9/7. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | 12 | var window: UIWindow? 13 | 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 19 | guard let _ = (scene as? UIWindowScene) else { return } 20 | } 21 | 22 | func sceneDidDisconnect(_ scene: UIScene) { 23 | // Called as the scene is being released by the system. 24 | // This occurs shortly after the scene enters the background, or when its session is discarded. 25 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 27 | } 28 | 29 | func sceneDidBecomeActive(_ scene: UIScene) { 30 | // Called when the scene has moved from an inactive state to an active state. 31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 32 | } 33 | 34 | func sceneWillResignActive(_ scene: UIScene) { 35 | // Called when the scene will move from an active state to an inactive state. 36 | // This may occur due to temporary interruptions (ex. an incoming phone call). 37 | } 38 | 39 | func sceneWillEnterForeground(_ scene: UIScene) { 40 | // Called as the scene transitions from the background to the foreground. 41 | // Use this method to undo the changes made on entering the background. 42 | } 43 | 44 | func sceneDidEnterBackground(_ scene: UIScene) { 45 | // Called as the scene transitions from the foreground to the background. 46 | // Use this method to save data, release shared resources, and store enough scene-specific state information 47 | // to restore the scene back to its current state. 48 | } 49 | 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /Flower/Flower/Tools/DateTool.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateTool.swift 3 | // Flower 4 | // 5 | // Created by kingsic on 2021/11/1. 6 | // 7 | 8 | import Foundation 9 | 10 | class DateTool: NSObject { 11 | /// 时间戳转换日期 12 | /// 13 | /// - parameter dateFormat: 日期格式:yyyy-MM-dd HH:mm:ss(默认格式为:yyyy年MM月dd日) 14 | /// 15 | /// - parameter timeStamp: 时间戳 16 | /// 17 | /// - returns: 转换后的日期 18 | class func convert(dateFormat: String = "yyyy年MM月dd日", timeStamp: String) -> (string: String, date: Date) { 19 | let dateFormatter = DateFormatter() 20 | dateFormatter.dateFormat = dateFormat 21 | 22 | let interval: TimeInterval = TimeInterval(timeStamp.prefix(10))! 23 | let date = Date(timeIntervalSince1970: interval) 24 | let dateString = dateFormatter.string(from: date as Date) 25 | 26 | return (dateString, date) 27 | } 28 | 29 | /// 当前时间 30 | /// 31 | /// - parameter dateFormat: 日期格式:yyyy-MM-dd HH:mm:ss(默认格式为:yyyy-MM-dd HH:mm:ss) 32 | /// 33 | /// - returns: 转换后的日期 34 | class func currentTime(dateFormat: String = "yyyy-MM-dd HH:mm:ss") -> (string: String, date: Date) { 35 | let dateFormatter = DateFormatter() 36 | dateFormatter.dateFormat = dateFormat 37 | let dateString = dateFormatter.string(from: Date()) 38 | let date = dateFormatter.date(from: dateString)! 39 | return (dateString, date) 40 | } 41 | 42 | /// 时间比较 43 | /// 44 | /// - parameter dateFormat: 日期格式:yyyy-MM-dd HH:mm:ss(默认格式为:yyyy-MM-dd HH:mm:ss) 45 | /// 46 | /// - parameter time: 相比较时间 47 | /// 48 | /// - parameter otherTime: 相比较时间 49 | /// 50 | /// - returns: 比较后的结果(1:代表time大于otherTime;0:代表time等于otherTime;-1:代表time小于otherTime) 51 | class func compare(dateFormat: String = "yyyy-MM-dd HH:mm:ss", time: Date, otherTime: Date) -> Int { 52 | let dateFormatter = DateFormatter() 53 | dateFormatter.dateFormat = dateFormat 54 | 55 | let stringTime = dateFormatter.string(from: time) 56 | let date = dateFormatter.date(from: stringTime) 57 | 58 | let stringOtherTime = dateFormatter.string(from: otherTime) 59 | let otherDate = dateFormatter.date(from: stringOtherTime) 60 | 61 | let result: ComparisonResult = date!.compare(otherDate!) 62 | if result == .orderedDescending { // time 大于 otherTime 63 | return 1 64 | } else if result == .orderedAscending { // time 小于 otherTime 65 | return -1 66 | } else { // 完全相等 67 | return 0 68 | } 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /Flower/Flower/Tools/GCDAsync.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GCDAsync.swift 3 | // GCDAsync 4 | // 5 | // Created by kingsic on 2020/12/31. 6 | // Copyright © 2020 kingsic. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public typealias Task = () -> Void 12 | 13 | public struct GCDAsync { 14 | /// 延迟函数 15 | @discardableResult 16 | public static func delay(_ time: Double, _ block: @escaping Task) -> DispatchWorkItem { 17 | let item = DispatchWorkItem(block: block) 18 | DispatchQueue.main.asyncAfter(deadline: .now() + time, execute: item) 19 | return item 20 | } 21 | 22 | /// 异步延迟函数 23 | /// 24 | /// - parameter time: 延迟时间 25 | /// - parameter task: 延迟执行任务 26 | @discardableResult 27 | public static func asyncDelay(_ time: Double, _ task: @escaping Task) -> DispatchWorkItem { 28 | _asyncDelay(time, task) 29 | } 30 | 31 | /// 异步延迟函数执行完后再回到主线程 32 | /// 33 | /// - parameter time: 延迟时间 34 | /// - parameter task: 延迟执行任务 35 | /// - parameter mainTask: 主线程执行任务 36 | @discardableResult 37 | public static func asyncDelay(_ time: Double, _ task: @escaping Task, mainTask: @escaping Task) -> DispatchWorkItem { 38 | _asyncDelay(time, task, mainTask) 39 | } 40 | 41 | /// 异步执行函数 42 | public static func async(_ task: @escaping Task) { 43 | _async(task) 44 | } 45 | /// 异步执行函数后再回到主线程 46 | /// 47 | /// - parameter task: 异步执行任务 48 | /// - parameter mainTask: 主线程执行任务 49 | public static func async(_ task: @escaping Task, mainTask: @escaping Task) { 50 | _async(task, mainTask) 51 | } 52 | } 53 | 54 | // MARK: 内部方法 55 | private extension GCDAsync { 56 | /// 异步函数 57 | /// 58 | /// - parameter block: 异步执行任务 59 | /// - parameter mainTask: 主线程执行任务 60 | private static func _async(_ block: @escaping Task, _ mainTask: Task? = nil) { 61 | let item = DispatchWorkItem(block: block) 62 | DispatchQueue.global().async(execute: item) 63 | if let main = mainTask { 64 | item.notify(queue: DispatchQueue.main, execute: main) 65 | } 66 | } 67 | 68 | /// 异步延迟函数 69 | /// 70 | /// - parameter block: 异步执行任务 71 | /// - parameter mainTask: 主线程执行任务 72 | private static func _asyncDelay(_ time: Double, _ block: @escaping Task, _ mainTask: Task? = nil) -> DispatchWorkItem { 73 | let item = DispatchWorkItem(block: block) 74 | DispatchQueue.global().asyncAfter(deadline: .now() + time, execute: item) 75 | if let main = mainTask { 76 | item.notify(queue: DispatchQueue.main, execute: main) 77 | } 78 | return item 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/Example/ItemsViewVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // ItemsViewVC.m 3 | // SGFastfishExample 4 | // 5 | // Created by kingsic on 2020/12/22. 6 | // Copyright © 2020 kingsic. All rights reserved. 7 | // 8 | 9 | #import "ItemsViewVC.h" 10 | #import "SGItemsView.h" 11 | 12 | @interface ItemsViewVC () 13 | 14 | @end 15 | 16 | @implementation ItemsViewVC 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | // Do any additional setup after loading the view. 21 | self.navigationItem.title = @"SGItemsView"; 22 | self.view.backgroundColor = [UIColor whiteColor]; 23 | 24 | NSArray *images = @[@"luckdraw_icon", @"luckdraw_icon", @"luckdraw_icon", @"luckdraw_icon", @"luckdraw_icon", @"luckdraw_icon", @"luckdraw_icon", @"luckdraw_icon"]; 25 | SGItemsView *items = [[SGItemsView alloc] init]; 26 | items.frame = CGRectMake(0, 100, self.view.frame.size.width, 70); 27 | items.backgroundColor = [UIColor greenColor]; 28 | items.titles = @[@"粉丝", @"喜欢", @"我的", @"评论", @"粉丝", @"喜欢", @"我的", @"评论"]; 29 | items.itemSize = CGSizeMake(90, 70); 30 | [self.view addSubview:items]; 31 | items.scrollDirectionHorizontal = YES; 32 | items.configureImgViewBlock = ^(UIImageView * _Nonnull imageView, NSInteger index) { 33 | imageView.image = [UIImage imageNamed:images[index]]; 34 | }; 35 | 36 | NSArray *images2 = @[@"luckdraw_icon", @"luckdraw_icon", @"luckdraw_icon", @"luckdraw_icon", @"luckdraw_icon", @"luckdraw_icon", @"luckdraw_icon", @"luckdraw_icon"]; 37 | SGItemsView *items2 = [[SGItemsView alloc] init]; 38 | items2.frame = CGRectMake(0, CGRectGetMaxY(items.frame) + 50, self.view.frame.size.width, 170); 39 | items2.backgroundColor = [UIColor greenColor]; 40 | items2.titles = @[@"粉丝", @"喜欢", @"我的", @"评论", @"粉丝", @"喜欢", @"我的", @"评论"]; 41 | items2.itemSize = CGSizeMake((self.view.frame.size.width - 40) / 4, 65); 42 | [self.view addSubview:items2]; 43 | items2.configureImgViewBlock = ^(UIImageView * _Nonnull imageView, NSInteger index) { 44 | imageView.image = [UIImage imageNamed:images2[index]]; 45 | }; 46 | items2.itemClickBlock = ^(NSInteger index) { 47 | NSLog(@"index - - %ld", index); 48 | }; 49 | items2.contentInset = UIEdgeInsetsMake(20, 20, 20, 20); 50 | [items2 setItemTitleColor:[UIColor redColor] forIndex:1]; 51 | [items2 setItemTitleColor:[UIColor redColor] forIndex:5]; 52 | } 53 | 54 | /* 55 | #pragma mark - Navigation 56 | 57 | // In a storyboard-based application, you will often want to do a little preparation before navigation 58 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 59 | // Get the new view controller using [segue destinationViewController]. 60 | // Pass the selected object to the new view controller. 61 | } 62 | */ 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /FlowerObjc/SourcesObjc/UIKit/SGActionSheet.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGActionSheet.h 3 | // SGActionSheet 4 | // 5 | // Created by kingsic on 2019/6/27. 6 | // Copyright © 2019年 kingsic. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SGActionSheetConfigure : NSObject 12 | /** 类方法 */ 13 | + (instancetype)configure; 14 | /** 标题文字字号大小,默认 15 号字体 */ 15 | @property (nonatomic, strong) UIFont *titleFont; 16 | /** 标题文字颜色,默认为浅灰色 */ 17 | @property (nonatomic, strong) UIColor *titleColor; 18 | /** 其他文字字号大小,默认 17 号字体 */ 19 | @property (nonatomic, strong) UIFont *otherFont; 20 | /** 其他文字颜色,默认为黑色 */ 21 | @property (nonatomic, strong) UIColor *otherColor; 22 | /** 取消文字字号大小,默认 17 号字体 */ 23 | @property (nonatomic, strong) UIFont *cancelFont; 24 | /** 取消文字颜色,默认为黑色 */ 25 | @property (nonatomic, strong) UIColor *cancelColor; 26 | /** cell height, default 44.0f */ 27 | @property (nonatomic, assign) CGFloat cellHeight; 28 | /** 是否需要遮盖背景色,默认为 YES */ 29 | @property (nonatomic, assign) BOOL cover; 30 | /** 遮盖背景颜色,默认为黑色alpha = 0.3(cover = NO 时不起作用)*/ 31 | @property (nonatomic, strong) UIColor *coverColor; 32 | /** SGActionSheet 是否需要穿透效果,默认为 YES */ 33 | @property (nonatomic, assign) BOOL penetrationEffect; 34 | /** SGActionSheet 顶部圆角大小,默认为 10.0f(取值范围为:0~30.0f)*/ 35 | @property (nonatomic, assign) CGFloat cornerRadius; 36 | 37 | @end 38 | 39 | 40 | typedef void(^SGActionSheetOtherTitleClickBlock)(NSInteger index); 41 | 42 | @interface SGActionSheet : UIView 43 | /** 不带标题、内有取消按钮的对象创建方法 */ 44 | - (instancetype)initWithOtherTitles:(NSArray *)otherTitles configure:(SGActionSheetConfigure *)configure; 45 | /** 不带标题、内有取消按钮的类方法 */ 46 | + (instancetype)actionSheetWithOtherTitles:(NSArray *)otherTitles configure:(SGActionSheetConfigure *)configure; 47 | /** 带标题、内有取消按钮的对象方法 */ 48 | - (instancetype)initWithTitle:(NSString *)title otherTitles:(NSArray *)otherTitles configure:(SGActionSheetConfigure *)configure; 49 | /** 带标题、内有取消按钮的类方法 */ 50 | + (instancetype)actionSheetWithTitle:(NSString *)title otherTitles:(NSArray *)otherTitles configure:(SGActionSheetConfigure *)configure; 51 | /** 对象方法 */ 52 | - (instancetype)initWithTitle:(NSString *)title cancelTitle:(NSString *)cancelTitle otherTitles:(NSArray *)otherTitles configure:(SGActionSheetConfigure *)configure; 53 | /** 类方法 */ 54 | + (instancetype)actionSheetWithTitle:(NSString *)title cancelTitle:(NSString *)cancelTitle otherTitles:(NSArray *)otherTitles configure:(SGActionSheetConfigure *)configure; 55 | /** 弹出 SGActionSheet */ 56 | - (void)actionSheet; 57 | 58 | /** 其他按钮点击回调函数 */ 59 | @property (nonatomic, copy) SGActionSheetOtherTitleClickBlock otherTitleClickBlock; 60 | 61 | /** 根据下标重置其他标题颜色 */ 62 | - (void)resetOtherTitleColor:(UIColor *)color forIndex:(NSInteger)index; 63 | /** 根据下标为其他标题添加图片 */ 64 | - (void)addOtherTitleWithImageName:(NSString *)imageName spacing:(CGFloat)spacing forIndex:(NSInteger)index; 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // FlowerObjc 4 | // 5 | // Created by kingsic on 2021/9/7. 6 | // 7 | 8 | #import "ViewController.h" 9 | #import "LabelVC.h" 10 | #import "TextViewVC.h" 11 | #import "TagsViewVC.h" 12 | #import "ItemsViewVC.h" 13 | #import "SGActionSheet.h" 14 | 15 | @interface ViewController () 16 | @property (weak, nonatomic) IBOutlet UITableView *tableView; 17 | @property (nonatomic, strong) NSArray *dataSource; 18 | @property (nonatomic, strong) NSArray *dataSourceVC; 19 | @end 20 | 21 | @implementation ViewController 22 | 23 | - (void)viewDidLoad { 24 | [super viewDidLoad]; 25 | // Do any additional setup after loading the view. 26 | self.dataSource = @[@"SGLabel", @"SGTextView", @"SGTagsView", @"SGItemsView", @"SGActionSheet"]; 27 | 28 | LabelVC *labVC = [[LabelVC alloc] init]; 29 | TextViewVC *textVC = [[TextViewVC alloc] init]; 30 | TagsViewVC *tagsVC = [[TagsViewVC alloc] init]; 31 | ItemsViewVC *itemsVC = [[ItemsViewVC alloc] init]; 32 | self.dataSourceVC = @[labVC, textVC, tagsVC, itemsVC]; 33 | 34 | [self configureTableView]; 35 | } 36 | - (void)configureTableView { 37 | [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cellID"]; 38 | self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero]; 39 | } 40 | 41 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 42 | return self.dataSource.count; 43 | } 44 | 45 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 46 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cellID" forIndexPath:indexPath]; 47 | cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 48 | cell.backgroundColor = [UIColor clearColor]; 49 | cell.selectionStyle = UITableViewCellSelectionStyleNone; 50 | cell.textLabel.text = self.dataSource[indexPath.row]; 51 | return cell; 52 | } 53 | 54 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 55 | 56 | if (self.dataSource.count - 1 == indexPath.row) { 57 | [self sheetView]; 58 | return; 59 | } 60 | 61 | [self.navigationController pushViewController:self.dataSourceVC[indexPath.row] animated:YES]; 62 | } 63 | 64 | - (void)sheetView { 65 | SGActionSheetConfigure *asc = [SGActionSheetConfigure configure]; 66 | asc.cellHeight = 50; 67 | SGActionSheet *as = [[SGActionSheet alloc] initWithTitle:@"退出后不会删除任何历史数据,下次登录依然可以使用本账号" cancelTitle:@"取消" otherTitles:@[@"退出登录"] configure:asc]; 68 | as.otherTitleClickBlock = ^(NSInteger index) { 69 | NSLog(@"index - - %ld", index); 70 | }; 71 | [as resetOtherTitleColor:[UIColor redColor] forIndex:0]; 72 | [as actionSheet]; 73 | } 74 | 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /Flower/Flower/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Flower/Flower/Tools/ImageDownload.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageDownload.swift 3 | // ImageDownload 4 | // 5 | // Created by kingsic on 2020/10/27. 6 | // Copyright © 2020 kingsic. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Download complete callback block 12 | public typealias DownloadCompleteBlock = ((UIImage) -> ())? 13 | 14 | public class ImageDownload: NSObject { 15 | /// Singleton 16 | public static let shared = ImageDownload() 17 | 18 | /// Memory cache, storing the dictionary of UIImage object 19 | private var images = [String: UIImage]() 20 | 21 | /// A dictionary that stores BlockOperation cache objects 22 | private var operations = [String: BlockOperation]() 23 | 24 | /// To prevent duplicate queue creation 25 | private var queue = OperationQueue() 26 | 27 | /// Download image 28 | /// 29 | /// - parameter urlString: Image address 30 | /// - parameter complete: Download complete callback block 31 | public func downloadImage(urlString: String, complete: DownloadCompleteBlock) { 32 | let image = images[urlString] 33 | if let tempImage = image { 34 | if complete != nil { 35 | complete!(tempImage) 36 | } 37 | } else { 38 | let cachePath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).last 39 | let imageName = (urlString as NSString).lastPathComponent 40 | let fullPath = cachePath! + "/" + imageName 41 | let imageData = NSData(contentsOfFile: fullPath) 42 | 43 | if imageData != nil { 44 | let image = UIImage(data: imageData! as Data) 45 | if complete != nil { 46 | complete!(image!) 47 | } 48 | images[urlString] = image 49 | } else { 50 | var blockOperation = operations[urlString] 51 | if blockOperation == nil { 52 | blockOperation = BlockOperation(block: { 53 | guard let url = URL.init(string: urlString) else { return } 54 | guard let imageData = NSData(contentsOf: url) else { return } 55 | let image = UIImage(data: imageData as Data) 56 | if image == nil { 57 | self.operations.removeValue(forKey: urlString) 58 | return 59 | } 60 | OperationQueue.main.addOperation { 61 | if complete != nil { 62 | complete!(image!) 63 | } 64 | } 65 | self.images[urlString] = image 66 | imageData.write(toFile: fullPath, atomically: true) 67 | self.operations.removeValue(forKey: urlString) 68 | }) 69 | 70 | operations[urlString] = blockOperation 71 | queue.addOperation(blockOperation!) 72 | } 73 | } 74 | } 75 | } 76 | 77 | /// Cancel all current operations 78 | public func cancelAll() { 79 | images.removeAll() 80 | queue.cancelAllOperations() 81 | } 82 | 83 | 84 | } 85 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Flower/Flower/Example/BaseCollectionViewVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseCollectionViewVC.swift 3 | // Flower 4 | // 5 | // Created by kingsic on 2021/9/8. 6 | // 7 | 8 | import UIKit 9 | 10 | class BaseCollectionViewVC: UIViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | view.backgroundColor = .white 16 | navigationItem.title = "SGBaseCollectionView" 17 | // Do any additional setup after loading the view. 18 | 19 | 20 | let baseItem = SGBaseCollectionView() 21 | baseItem.frame = CGRect.init(x: 20, y: navBarHeight + 20, width: screenWidth - 40, height: 90) 22 | baseItem.backgroundColor = .green 23 | baseItem.itemSize = CGSize.init(width: 130, height: 80) 24 | view.addSubview(baseItem) 25 | baseItem.register(BaseItemsCell.self, reuseIdentifier: "BaseCellID") 26 | baseItem.delegate = self 27 | baseItem.dataSource = self 28 | baseItem.scrollDirectionHorizontal = true 29 | baseItem.contentInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20) 30 | // baseItem.minimumLineSpacing = 20 31 | 32 | let baseItem1 = SGBaseCollectionView() 33 | baseItem1.frame = CGRect.init(x: 20, y: baseItem.frame.maxY + 20, width: screenWidth - 40, height: 90) 34 | baseItem1.backgroundColor = .green 35 | let w: CGFloat = screenWidth / 5 36 | baseItem1.itemSize = CGSize.init(width: w, height: w) 37 | view.addSubview(baseItem1) 38 | baseItem1.register(BaseItemsCell.self, reuseIdentifier: "BaseCellID") 39 | baseItem1.delegate = self 40 | baseItem1.dataSource = self 41 | baseItem1.scrollDirectionHorizontal = true 42 | baseItem1.pagingEnabled = true 43 | let s: CGFloat = 0.5 * (90 - w) 44 | baseItem.contentInset = UIEdgeInsets(top: s, left: 0, bottom: s, right: 0) 45 | } 46 | 47 | } 48 | 49 | extension BaseCollectionViewVC: SGBaseCollectionViewDataSource, SGBaseCollectionViewDelegate { 50 | func collectionView(_ baseCollectionView: SGBaseCollectionView, numberOfItems: Int) -> Int { 51 | return 6 52 | } 53 | 54 | func collectionView(_ baseCollectionView: SGBaseCollectionView, cell: UICollectionViewCell, cellForItemAt index: Int) { 55 | (cell as! BaseItemsCell).btn.setTitle("index - \(index)", for: .normal) 56 | } 57 | 58 | func collectionView(_ baseCollectionView: SGBaseCollectionView, didSelectItemAt index: Int) { 59 | print("当前点击的item下标值为:\(index)") 60 | } 61 | } 62 | 63 | class BaseItemsCell: UICollectionViewCell { 64 | var btn = UIButton() 65 | 66 | override init(frame: CGRect) { 67 | super.init(frame: frame) 68 | backgroundColor = .orange 69 | 70 | let height: CGFloat = 20 71 | let y: CGFloat = 0.5 * (frame.size.height - height) 72 | 73 | btn.frame = CGRect(x: 10, y: y, width: frame.size.width - 20, height: height) 74 | btn.setTitle("btn", for: .normal) 75 | btn.backgroundColor = .red 76 | btn.addTarget(self, action: #selector(btn_action), for: .touchUpInside) 77 | self.addSubview(btn) 78 | } 79 | 80 | required init?(coder: NSCoder) { 81 | fatalError("init(coder:) has not been implemented") 82 | } 83 | 84 | @objc func btn_action() { 85 | print("btn_action") 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Flower/Sources/SGTextView/SGTextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SGTextView.swift 3 | // Flower 4 | // 5 | // Created by kingsic on 2020/9/22. 6 | // Copyright © 2020 kingsic. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class SGTextView: UITextView { 12 | /// PlaceHolder 13 | public var placeHolder: String? { 14 | didSet { 15 | placeHolderLabel.text = placeHolder 16 | } 17 | } 18 | 19 | /// PlaceHolder Color 20 | public var placeHolderColor: UIColor? { 21 | didSet { 22 | placeHolderLabel.textColor = placeHolderColor 23 | } 24 | } 25 | 26 | /// Limit input words 27 | public var limitNumber: Int? 28 | 29 | /// PlaceHolder Label 30 | private lazy var placeHolderLabel: UILabel = { 31 | $0.font = UIFont.systemFont(ofSize: 12) 32 | $0.text = "请输入内容~" 33 | $0.textColor = UIColor.lightGray 34 | $0.numberOfLines = 0 35 | return $0 36 | }(UILabel()) 37 | 38 | public override var font: UIFont? { 39 | didSet { 40 | if font != nil { 41 | // 让在属性哪里修改的字体,赋给给我们占位label 42 | placeHolderLabel.font = font 43 | } 44 | } 45 | } 46 | 47 | public override var text: String? { 48 | didSet { 49 | // 根据文本是否有内容而显示占位label 50 | placeHolderLabel.isHidden = hasText 51 | } 52 | } 53 | 54 | // frame 55 | override init(frame: CGRect, textContainer: NSTextContainer?) { 56 | super.init(frame: frame, textContainer: textContainer) 57 | setupUI() 58 | } 59 | // xib 60 | required init?(coder aDecoder: NSCoder) { 61 | super.init(coder: aDecoder) 62 | setupUI() 63 | } 64 | 65 | // 添加控件, 设置约束 66 | fileprivate func setupUI() { 67 | // 监听内容的通知 68 | NotificationCenter.default.addObserver(self, selector: #selector(SGTextView.valueChange), name: UITextView.textDidChangeNotification, object: nil) 69 | // UITextViewTextDidChangeNotification 70 | // 添加控件 71 | addSubview(placeHolderLabel) 72 | 73 | // 设置约束,使用系统的约束 74 | placeHolderLabel.translatesAutoresizingMaskIntoConstraints = false 75 | 76 | addConstraint(NSLayoutConstraint(item: placeHolderLabel, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1, constant: -10)) 77 | } 78 | 79 | // 内容改变的通知方法 80 | @objc fileprivate func valueChange() { 81 | // 占位文字的显示与隐藏 82 | placeHolderLabel.isHidden = hasText 83 | 84 | // 字数限制处理 85 | if limitNumber != nil { 86 | 87 | guard limitNumber! > 0 else { 88 | return 89 | } 90 | 91 | if self.text!.count > limitNumber! { 92 | self.text = (self.text! as NSString).substring(to: limitNumber!) 93 | } 94 | } 95 | } 96 | 97 | public override func layoutSubviews() { 98 | super.layoutSubviews() 99 | // 设置占位文字的坐标 100 | placeHolderLabel.frame.origin.x = 5 101 | placeHolderLabel.frame.origin.y = 7 102 | } 103 | 104 | 105 | deinit { 106 | NotificationCenter.default.removeObserver(self) 107 | } 108 | 109 | } 110 | 111 | -------------------------------------------------------------------------------- /Flower/Flower/Example/CycleScrollViewVC.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Flower/Flower/Example/FileVC.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /FlowerObjc/SourcesObjc/UIKit/SGTextView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGTextView.m 3 | // SGTextView 4 | // 5 | // Created by kingsic on 2020/12/11. 6 | // Copyright © 2020 kingsic. All rights reserved. 7 | // 8 | 9 | #import "SGTextView.h" 10 | 11 | @interface SGTextView () 12 | @property (nonatomic, strong) UILabel *placeholderLabel; 13 | @property (nonatomic, assign) BOOL addPlaceholderNotification; 14 | @property (nonatomic, assign) BOOL addLimitNumberNotification; 15 | @end 16 | 17 | @implementation SGTextView 18 | 19 | - (void)layoutSubviews { 20 | [super layoutSubviews]; 21 | 22 | CGFloat x = 6; 23 | CGFloat y = 0; 24 | CGFloat w = self.frame.size.width - 2 * x; 25 | CGFloat h = 0; 26 | if (self.addPlaceholderNotification) { 27 | if (self.font == nil) { 28 | y = 6.5; 29 | h = [self P_calculateHeightWithString:_placeholder font:_placeholderLabel.font width:w]; 30 | } else { 31 | y = 8; 32 | h = [self P_calculateHeightWithString:_placeholder font:self.font width:w]; 33 | } 34 | _placeholderLabel.frame = CGRectMake(x, y, w, h); 35 | } 36 | } 37 | 38 | - (UILabel *)placeholderLabel { 39 | if (!_placeholderLabel) { 40 | _placeholderLabel = [[UILabel alloc] init]; 41 | _placeholderLabel.text = _placeholder; 42 | _placeholderLabel.numberOfLines = 0; 43 | _placeholderLabel.textColor = [UIColor lightGrayColor]; 44 | if (self.font == nil) { 45 | _placeholderLabel.font = [UIFont systemFontOfSize:13]; 46 | } else { 47 | _placeholderLabel.font = self.font; 48 | } 49 | } 50 | return _placeholderLabel; 51 | } 52 | 53 | - (void)setPlaceholder:(NSString *)placeholder { 54 | _placeholder = placeholder; 55 | 56 | [self addSubview:self.placeholderLabel]; 57 | if (self.addPlaceholderNotification == NO && self.addLimitNumberNotification == NO) { 58 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChangeNotification) name:UITextViewTextDidChangeNotification object:self]; 59 | self.addPlaceholderNotification = YES; 60 | } else if (self.addPlaceholderNotification == NO && self.addLimitNumberNotification) { 61 | self.addPlaceholderNotification = YES; 62 | } 63 | } 64 | - (void)setPlaceholderColor:(UIColor *)placeholderColor { 65 | _placeholderColor = placeholderColor; 66 | _placeholderLabel.textColor = placeholderColor; 67 | } 68 | 69 | - (void)setLimitNumber:(NSInteger)limitNumber { 70 | _limitNumber = limitNumber; 71 | if (limitNumber <= 0) { 72 | @throw [NSException exceptionWithName:@"SGTextView" reason:@"【limitNumber属性值必须大于等于0】" userInfo:nil];; 73 | } 74 | if (self.addLimitNumberNotification == NO && self.addPlaceholderNotification == NO) { 75 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChangeNotification) name:UITextViewTextDidChangeNotification object:self]; 76 | self.addLimitNumberNotification = YES; 77 | } else if (self.addLimitNumberNotification == NO && self.addPlaceholderNotification) { 78 | self.addLimitNumberNotification = YES; 79 | } 80 | } 81 | 82 | - (void)textDidChangeNotification { 83 | if (self.addPlaceholderNotification) { 84 | _placeholderLabel.hidden = self.hasText; 85 | } 86 | if (self.addLimitNumberNotification) { 87 | if (_limitNumber > 0) { 88 | if (self.text.length > _limitNumber) { 89 | self.text = [self.text substringToIndex:_limitNumber]; 90 | } 91 | } 92 | } 93 | } 94 | 95 | - (void)dealloc { 96 | if (self.addPlaceholderNotification || self.addLimitNumberNotification) { 97 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 98 | } 99 | } 100 | 101 | 102 | /** 103 | * 根据字号大小、宽度及字符串上下间的间距计算出字符串的高 104 | */ 105 | - (CGFloat)P_calculateHeightWithString:(NSString *)string font:(UIFont *)font width:(CGFloat)width { 106 | NSDictionary *attributes = @{NSFontAttributeName : font}; 107 | return [string boundingRectWithSize:CGSizeMake(width, 0) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size.height; 108 | } 109 | 110 | @end 111 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/Example/TagsViewVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // TagsViewVC.m 3 | // SGFastfishExample 4 | // 5 | // Created by kingsic on 2020/12/22. 6 | // Copyright © 2020 kingsic. All rights reserved. 7 | // 8 | 9 | #import "TagsViewVC.h" 10 | #import "SGTagsView.h" 11 | 12 | @interface TagsViewVC () 13 | 14 | @end 15 | 16 | @implementation TagsViewVC 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | // Do any additional setup after loading the view. 21 | self.navigationItem.title = @"SGTagsView"; 22 | self.view.backgroundColor = [UIColor whiteColor]; 23 | 24 | SGTagsViewConfigure *configure = [SGTagsViewConfigure configure]; 25 | configure.tagsStyle = SGTagsStyleVertical; 26 | configure.cornerRadius = 15; 27 | configure.borderWidth = 2; 28 | configure.selectedColor = [UIColor whiteColor]; 29 | configure.borderColor = [UIColor redColor]; 30 | configure.selectedBorderColor = [UIColor greenColor]; 31 | configure.selectedBackgroundColor = [UIColor redColor]; 32 | 33 | NSArray *tags = @[@"这是单选标签", @"iPhone 8", @"iPhone 8P", @"iPhone X", @"iPhone XR", @"iPhone XS", @"iPhone XS Max"]; 34 | SGTagsView *tagsView = [SGTagsView tagsViewWithFrame:CGRectMake(0, 100, self.view.frame.size.width, 50) configure:configure]; 35 | tagsView.tags = tags; 36 | tagsView.backgroundColor = [UIColor greenColor]; 37 | [self.view addSubview:tagsView]; 38 | tagsView.singleSelectBlock = ^(SGTagsView * _Nonnull tagsView, NSString *tag, NSInteger index) { 39 | NSLog(@"%@ - - %ld", tag, index); 40 | }; 41 | tagsView.heightBlock = ^(SGTagsView *tagsView, CGFloat height) { 42 | NSLog(@"%.2f", height); 43 | 44 | SGTagsViewConfigure *mconfigure = [SGTagsViewConfigure configure]; 45 | mconfigure.multipleSelect = YES; 46 | mconfigure.borderWidth = 1.0; 47 | mconfigure.column = 2; 48 | mconfigure.bounces = YES; 49 | NSArray *mtags = @[@"多选且可滚动", @"iPhone 8", @"iPhone 8P", @"iPhone X", @"iPhone XR", @"iPhone XS", @"iPhone XS Max", @"iPhone 8", @"iPhone 8P", @"iPhone X", @"iPhone XR", @"iPhone XS", @"iPhone XS Max"]; 50 | SGTagsView *mtagsView = [SGTagsView tagsViewWithFrame:CGRectMake(0, CGRectGetMaxY(tagsView.frame) + 20, self.view.frame.size.width, 200) configure:mconfigure]; 51 | mtagsView.tags = mtags; 52 | mtagsView.tagIndexs = @[@"0", @"1"]; 53 | mtagsView.isFixedHeight = YES; 54 | mtagsView.backgroundColor = [UIColor greenColor]; 55 | [self.view addSubview:mtagsView]; 56 | mtagsView.multipleSelectBlock = ^(SGTagsView * _Nonnull tagsView, NSArray * _Nonnull tags, NSArray * _Nonnull indexs) { 57 | NSLog(@"%@ - - %@", tags, indexs); 58 | }; 59 | [mtagsView setImageName:@"luckdraw_icon" imagePositionStyle:(SGImagePositionStyleDefault) spacing:5 forIndex:0]; 60 | mtagsView.heightBlock = ^(SGTagsView *tagsView, CGFloat height) { 61 | SGTagsViewConfigure *otherConfigure = [SGTagsViewConfigure configure]; 62 | otherConfigure.contentInset = UIEdgeInsetsMake(0.01, 0.01, 0.01, 0.01); 63 | otherConfigure.horizontalSpacing = 0.01; 64 | otherConfigure.verticalSpacing = 0.01; 65 | otherConfigure.height = 80; 66 | otherConfigure.column = 5; 67 | NSArray *otherDataSource = @[@"美食", @"卖场便利", @"水果", @"跑腿代购", @"甜品饮品", @"星选好店", @"送药上门", @"大牌会吃", @"取送件", @"签到领红包"]; 68 | SGTagsView *otherTageView = [SGTagsView tagsViewWithFrame:CGRectMake(0, CGRectGetMaxY(mtagsView.frame) + 20, self.view.frame.size.width, 50) configure:otherConfigure]; 69 | otherTageView.tags = otherDataSource; 70 | NSArray *tia = @[@"luckdraw_icon", @"luckdraw_icon", @"luckdraw_icon", @"luckdraw_icon", @"luckdraw_icon", @"luckdraw_icon", @"luckdraw_icon", @"luckdraw_icon", @"luckdraw_icon", @"luckdraw_icon"]; 71 | [otherTageView setImageNames:tia imagePositionStyle:(SGImagePositionStyleTop) spacing:5]; 72 | [self.view addSubview:otherTageView]; 73 | }; 74 | }; 75 | } 76 | 77 | /* 78 | #pragma mark - Navigation 79 | 80 | // In a storyboard-based application, you will often want to do a little preparation before navigation 81 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 82 | // Get the new view controller using [segue destinationViewController]. 83 | // Pass the selected object to the new view controller. 84 | } 85 | */ 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /Flower/Flower/Example/CycleScrollViewVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CycleScrollViewVC.swift 3 | // Flower 4 | // 5 | // Created by kingsic on 2022/12/19. 6 | // 7 | 8 | import UIKit 9 | 10 | class CycleScrollViewVC: UIViewController { 11 | 12 | deinit { 13 | print("\(type(of: self)) - \(#function)") 14 | } 15 | 16 | let dataSource = ["SGCycleScrollView 支持主流轮播滚动", "像 UICollectionView 一样轻松使用", "完全支持自定义 Cell,不需要依赖任何第三方"] 17 | 18 | let xibDataSource = ["image_0", "image_1", "image_2"] 19 | 20 | @IBOutlet weak var XibCycleScrollView: SGCycleScrollView! 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | // Do any additional setup after loading the view. 26 | 27 | // cycleScrollView() 28 | 29 | configXibCycleScrollView() 30 | } 31 | 32 | func cycleScrollView() { 33 | let cycleView = SGCycleScrollView() 34 | cycleView.backgroundColor = .red 35 | 36 | cycleView.frame = CGRect(x: 0, y: 100, width: UIScreen.main.bounds.size.width, height: ceil(50.0)) 37 | cycleView.delegate = self 38 | cycleView.dataSource = self 39 | cycleView.timeInterval = 3 40 | cycleView.scrollDirection = .vertical 41 | cycleView.isScrollEnabled = false 42 | view.addSubview(cycleView) 43 | cycleView.register(CycleScrollViewCell.self, reuseIdentifier: "") 44 | } 45 | 46 | func configXibCycleScrollView() { 47 | XibCycleScrollView.delegate = self 48 | XibCycleScrollView.dataSource = self 49 | XibCycleScrollView.timeInterval = 3 50 | XibCycleScrollView.register(XibCycleScrollViewCell.self, reuseIdentifier: "") 51 | } 52 | 53 | } 54 | 55 | extension CycleScrollViewVC: SGCycleScrollViewDelegate, SGCycleScrollViewDataSource { 56 | func cycleScrollView(_ cycleScrollView: SGCycleScrollView, didSelectItemAt index: Int) { 57 | print("index - - \(index)") 58 | } 59 | 60 | // func cycleScrollView(_ cycleScrollView: SGCycleScrollView, didEndScrollingToItemAt index: Int) { 61 | // print("didEndScrollingToItemAt - \(index)") 62 | // } 63 | 64 | func numberOfItems(_ cycleScrollView: SGCycleScrollView) -> Int { 65 | return cycleScrollView == XibCycleScrollView ? xibDataSource.count : dataSource.count 66 | } 67 | 68 | func cycleScrollView(_ cycleScrollView: SGCycleScrollView, cell: UICollectionViewCell, cellForItemAt index: Int) { 69 | if cycleScrollView == XibCycleScrollView { 70 | let cell = cell as! XibCycleScrollViewCell 71 | cell.imgView.image = UIImage(named: xibDataSource[index]) 72 | } else { 73 | let cell = cell as! CycleScrollViewCell 74 | cell.btn.setTitle(dataSource[index], for: .normal) 75 | } 76 | } 77 | } 78 | 79 | class CycleScrollViewCell: UICollectionViewCell { 80 | var btn = UIButton() 81 | 82 | override init(frame: CGRect) { 83 | super.init(frame: frame) 84 | btn.backgroundColor = .red 85 | btn.addTarget(self, action: #selector(btn_action), for: .touchUpInside) 86 | btn.isUserInteractionEnabled = false 87 | btn.contentHorizontalAlignment = .left 88 | self.addSubview(btn) 89 | } 90 | 91 | override func layoutSubviews() { 92 | super.layoutSubviews() 93 | 94 | btn.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height) 95 | } 96 | 97 | required init?(coder: NSCoder) { 98 | fatalError("init(coder:) has not been implemented") 99 | } 100 | 101 | @objc func btn_action() { 102 | print("btn_action") 103 | } 104 | } 105 | 106 | class XibCycleScrollViewCell: UICollectionViewCell { 107 | var imgView = UIImageView() 108 | 109 | override init(frame: CGRect) { 110 | super.init(frame: frame) 111 | 112 | imgView.contentMode = .scaleAspectFill 113 | self.addSubview(imgView) 114 | } 115 | 116 | override func layoutSubviews() { 117 | super.layoutSubviews() 118 | 119 | imgView.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height) 120 | } 121 | 122 | required init?(coder: NSCoder) { 123 | fatalError("init(coder:) has not been implemented") 124 | } 125 | 126 | @objc func btn_action() { 127 | print("btn_action") 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Flower/Flower/Custom/SGDragView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SGDragView.swift 3 | // SGDragView 4 | // 5 | // Created by kingsic on 2021/12/16. 6 | // Copyright © 2021 kingsic. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public enum SGDragViewTop { 12 | case `default` 13 | case statusBar 14 | case navigationBar 15 | } 16 | 17 | public enum SGDragViewBottom { 18 | case `default` 19 | case safeArea 20 | case tabBar 21 | } 22 | 23 | 24 | public class SGDragView: UIView { 25 | /// 距离顶部的距离 26 | public var topDistance: SGDragViewTop = .default 27 | 28 | /// 距离底部的距离 29 | public var bottomDistance: SGDragViewBottom = .default 30 | 31 | /// 吸附到左右边侧需要的时间 32 | public var animateDuration: Double = 0.25 33 | 34 | 35 | /// 记录开始点 36 | fileprivate var beganPoint: CGPoint = .zero 37 | 38 | public override func touchesBegan(_ touches: Set, with event: UIEvent?) { 39 | super.touchesBegan(touches, with: event) 40 | 41 | let touch = touches.first 42 | beganPoint = (touch?.location(in: self))! 43 | } 44 | 45 | public override func touchesMoved(_ touches: Set, with event: UIEvent?) { 46 | let touch = touches.first! 47 | let currentPoint = touch.location(in: self) 48 | 49 | let offsetX = currentPoint.x - beganPoint.x 50 | let offsetY = currentPoint.y - beganPoint.y 51 | 52 | center = CGPoint(x: center.x + offsetX, y: center.y + offsetY) 53 | 54 | let superViewSize = superview?.frame.size 55 | let selfSize = frame.size 56 | 57 | if center.x > (superViewSize?.width)! - selfSize.width * 0.5 { 58 | let x = (superViewSize?.width)! - selfSize.width * 0.5 59 | center = CGPoint(x: x, y: center.y + offsetY) 60 | } else if (center.x < selfSize.width * 0.5) { 61 | let x = selfSize.width * 0.5 62 | center = CGPoint(x: x, y: center.y + offsetY) 63 | } 64 | 65 | if center.y > (superViewSize?.height)! - selfSize.height * 0.5 - bottom_y() { 66 | let y = (superViewSize?.height)! - selfSize.height * 0.5 - bottom_y() 67 | center = CGPoint(x: center.x, y: y) 68 | } else if center.y < selfSize.height * 0.5 + top_y() { 69 | let y = selfSize.height * 0.5 + top_y() 70 | center = CGPoint(x: center.x, y: y) 71 | } 72 | } 73 | 74 | public override func touchesEnded(_ touches: Set, with event: UIEvent?) { 75 | let superViewSize = superview?.frame.size 76 | let selfSize = frame.size 77 | let y = self.center.y - 0.5 * selfSize.height 78 | 79 | if center.x >= (superViewSize?.width)! * 0.5 { 80 | UIView.animate(withDuration: animateDuration) { 81 | let x = superViewSize!.width - selfSize.width 82 | self.frame = CGRect(x: x, y: y, width: selfSize.width, height: selfSize.height) 83 | } 84 | } else { 85 | UIView.animate(withDuration: animateDuration) { 86 | self.frame = CGRect(x: 0, y: y, width: selfSize.width, height: selfSize.height) 87 | } 88 | } 89 | } 90 | 91 | private func top_y() -> CGFloat { 92 | if topDistance == .default { 93 | return 0 94 | } else if topDistance == .statusBar { 95 | return SGDragView.statusBarHeight 96 | } else { 97 | return SGDragView.navBarHeight 98 | } 99 | } 100 | 101 | private func bottom_y() -> CGFloat { 102 | if bottomDistance == .default { 103 | return 0 104 | } else if bottomDistance == .safeArea { 105 | return SGDragView.safeAreaInsetBottom 106 | } else { 107 | return SGDragView.tabBarHeight 108 | } 109 | } 110 | 111 | } 112 | 113 | 114 | fileprivate extension SGDragView { 115 | /// Gets status bar height 116 | static var statusBarHeight: CGFloat { 117 | if #available(iOS 13, *) { 118 | return (UIApplication.shared.windows.first?.windowScene?.statusBarManager?.statusBarFrame.size.height)! 119 | } else { 120 | return UIApplication.shared.statusBarFrame.size.height 121 | } 122 | } 123 | 124 | /// Gets navigation bar height 125 | static var navBarHeight: CGFloat { return statusBarHeight + 44 } 126 | 127 | /// Gets tab bar height 128 | static var tabBarHeight: CGFloat { return statusBarHeight == 20 ? 49 : 83 } 129 | 130 | /// Gets bottom safeArea height 131 | static var safeAreaInsetBottom: CGFloat { return statusBarHeight == 20 ? 0 : 34 } 132 | } 133 | -------------------------------------------------------------------------------- /Flower/Flower/Example/ExpandTableViewVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExpandTableViewVC.swift 3 | // ExpandTableViewVC 4 | // 5 | // Created by kingsic on 2022/1/21. 6 | // 7 | 8 | import UIKit 9 | 10 | class ExpandTableViewVC: UIViewController { 11 | 12 | var dataSource: Array = [Any]() 13 | var sectionIndex: Array = [String]() 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | view.addSubview(tableView) 19 | 20 | let arr = ["Swift", "Objective-C"] 21 | let arr1 = ["Swift", "Objective-C", "Flutter"] 22 | 23 | dataSource.append(arr) 24 | dataSource.append(arr1) 25 | 26 | dataSource.forEach { _ in 27 | sectionIndex.append("1") 28 | } 29 | 30 | tableView.reloadData() 31 | } 32 | 33 | lazy var tableView: UITableView = { 34 | let tv = UITableView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height), style: .grouped) 35 | tv.delegate = self 36 | tv.dataSource = self 37 | tv.register(UITableViewCell.self, forCellReuseIdentifier: "cellID") 38 | tv.bounces = false 39 | return tv 40 | }() 41 | } 42 | 43 | extension ExpandTableViewVC: UITableViewDataSource { 44 | func numberOfSections(in tableView: UITableView) -> Int { 45 | return dataSource.count 46 | } 47 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 48 | let array: Array = dataSource[section] as! Array 49 | return array.count 50 | } 51 | 52 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 53 | let cell = tableView.dequeueReusableCell(withIdentifier: "cellID", for: indexPath) 54 | let array: Array = dataSource[indexPath.section] as! Array 55 | cell.textLabel?.text = (array[indexPath.row] as! String) 56 | cell.backgroundColor = .red 57 | return cell 58 | } 59 | } 60 | 61 | extension ExpandTableViewVC: UITableViewDelegate { 62 | func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 63 | let tempView = UIView() 64 | tempView.backgroundColor = .green 65 | 66 | let rightBtn = UIButton(type: .custom) 67 | rightBtn.addTarget(self, action: #selector(section_rightBtn_action), for: .touchUpInside) 68 | rightBtn.frame = CGRect(x: UIScreen.main.bounds.size.width - 100, y: 0, width: 100, height: 52) 69 | rightBtn.setTitle("点我啊", for: .normal) 70 | rightBtn.backgroundColor = .black 71 | rightBtn.tag = section 72 | tempView.addSubview(rightBtn) 73 | 74 | let leftBtn = UIButton(type: .custom) 75 | leftBtn.contentHorizontalAlignment = .left 76 | leftBtn.titleLabel?.font = .boldSystemFont(ofSize: 16) 77 | leftBtn.setTitle("第\(section + 1)章", for: .normal) 78 | leftBtn.setTitleColor(.red, for: .normal) 79 | leftBtn.frame = CGRect(x: 0, y: 0, width: 100, height: 52) 80 | tempView.addSubview(leftBtn) 81 | return tempView 82 | } 83 | func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { 84 | return UIView() 85 | } 86 | func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 87 | return 52 88 | } 89 | func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { 90 | return 0.01 91 | } 92 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 93 | if (sectionIndex[indexPath.section] as NSString).isEqual(to: "0") { 94 | return 0 95 | } else { 96 | return 66 97 | } 98 | } 99 | 100 | 101 | @objc func section_rightBtn_action(btn: UIButton) { 102 | btn.isSelected = !btn.isSelected 103 | 104 | let tempIndex = btn.tag 105 | let tempArr: Array = dataSource[tempIndex] as! Array 106 | var indexArray: Array = [] 107 | 108 | for (index, _) in tempArr.enumerated() { 109 | let indexPath = NSIndexPath(row: index, section: tempIndex) 110 | indexArray.append(indexPath as IndexPath) 111 | } 112 | 113 | if (sectionIndex[tempIndex] as NSString).isEqual(to: "0") { 114 | sectionIndex[tempIndex] = "1" 115 | tableView.reloadRows(at: indexArray, with: UITableView.RowAnimation.none) 116 | } else { 117 | sectionIndex[tempIndex] = "0" 118 | tableView.reloadRows(at: indexArray, with: UITableView.RowAnimation.none) 119 | } 120 | } 121 | } 122 | 123 | -------------------------------------------------------------------------------- /FlowerObjc/SourcesObjc/UIKit/SGTagsView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGTagsView.h 3 | // SGTagsView 4 | // 5 | // Created by kingsic on 2019/6/18. 6 | // Copyright © 2019年 kingsic. All rights reserved. 7 | // 8 | 9 | #import 10 | @class SGTagsView; 11 | 12 | typedef enum : NSUInteger { 13 | /** 标签均分垂直布局样式,默认 */ 14 | SGTagsStyleEquableVertical, 15 | /** 标签自适应垂直布局样式 */ 16 | SGTagsStyleVertical, 17 | /** 标签水平布局样式*/ 18 | SGTagsStyleHorizontal, 19 | } SGTagsStyle; 20 | 21 | typedef enum : NSUInteger { 22 | /** 内容居中样式,默认 */ 23 | SGControlContentHorizontalAlignmentCenter, 24 | /** 内容居左样式 */ 25 | SGControlContentHorizontalAlignmentLeft, 26 | /** 内容居右样式*/ 27 | SGControlContentHorizontalAlignmentRight, 28 | } SGControlContentHorizontalAlignment; 29 | 30 | @interface SGTagsViewConfigure : NSObject 31 | /** 类方法 */ 32 | + (instancetype)configure; 33 | /** SGTagsView 内部标签布局样式 */ 34 | @property (nonatomic, assign) SGTagsStyle tagsStyle; 35 | /** 标签是否能够被选择,默认为 YES */ 36 | @property (nonatomic, assign) BOOL selected; 37 | /** SGTagsView 是否需要弹性效果,默认为 NO */ 38 | @property (nonatomic, assign) BOOL bounces; 39 | /** 标签是否支持多选,默认为 NO */ 40 | @property (nonatomic, assign) BOOL multipleSelect; 41 | /** 标签文字字号大小,默认 15 号字体 */ 42 | @property (nonatomic, strong) UIFont *font; 43 | /** 普通状态下标签文字颜色,默认为黑色 */ 44 | @property (nonatomic, strong) UIColor *color; 45 | /** 选中状态下标签文字颜色,默认为红色 */ 46 | @property (nonatomic, strong) UIColor *selectedColor; 47 | /** 普通状态下标签背景颜色,默认为浅灰色 */ 48 | @property (nonatomic, strong) UIColor *backgroundColor; 49 | /** 选中状态下标签背景颜色,默认为白色 */ 50 | @property (nonatomic, strong) UIColor *selectedBackgroundColor; 51 | /** 标签边框宽度,默认为 0.0f,取值范围为:0~2.0f */ 52 | @property (nonatomic, assign) CGFloat borderWidth; 53 | /** 普通状态下标签边框颜色,默认为白色 */ 54 | @property (nonatomic, strong) UIColor *borderColor; 55 | /** 选中状态下标签边框颜色,默认为红色 */ 56 | @property (nonatomic, strong) UIColor *selectedBorderColor; 57 | /** 标签圆角大小,默认为 0.0f */ 58 | @property (nonatomic, assign) CGFloat cornerRadius; 59 | /** 标签水平之间的间距,默认为 20.0f */ 60 | @property (nonatomic, assign) CGFloat horizontalSpacing; 61 | /** 标签垂直之间的间距,默认为 20.0f */ 62 | @property (nonatomic, assign) CGFloat verticalSpacing; 63 | /** 标签额外增加的宽度,默认为 40.0f */ 64 | @property (nonatomic, assign) CGFloat additionalWidth; 65 | /** SGTagsViewStyleVertical 样式下标签的高度,默认为 30.0f */ 66 | @property (nonatomic, assign) CGFloat height; 67 | /** SGTagsViewStyleEquable 样式下标签的列数,默认为 3 */ 68 | @property (nonatomic, assign) NSInteger column; 69 | /** 标签距父视图内边距,默认为 UIEdgeInsetsMake(10, 10, 10, 10) */ 70 | @property (nonatomic, assign) UIEdgeInsets contentInset; 71 | /** 标签内容水平对齐样式,默认为 center 样式 */ 72 | @property (nonatomic, assign) SGControlContentHorizontalAlignment contentHorizontalAlignment; 73 | /** contentHorizontalAlignment 属性为 left 或 right 样式下,距离标签内边距的距离,默认为 5.0f */ 74 | @property (nonatomic, assign) CGFloat contentHorizontalAlignmentSpacing; 75 | @end 76 | 77 | typedef enum : NSUInteger { 78 | /// 图片在左,文字在右 79 | SGImagePositionStyleDefault, 80 | /// 图片在右,文字在左 81 | SGImagePositionStyleRight, 82 | /// 图片在上,文字在下 83 | SGImagePositionStyleTop, 84 | /// 图片在下,文字在上 85 | SGImagePositionStyleBottom, 86 | } SGImagePositionStyle; 87 | 88 | typedef void(^SGTagsViewHeightBlock)(SGTagsView *tagsView, CGFloat height); 89 | typedef void(^SGTagsViewSingleSelectBlock)(SGTagsView *tagsView, NSString *tag, NSInteger index); 90 | typedef void(^SGTagsViewMultipleSelectBlock)(SGTagsView *tagsView, NSArray *tags, NSArray *indexs); 91 | 92 | @interface SGTagsView : UIView 93 | /** 对象方法 */ 94 | - (instancetype)initWithFrame:(CGRect)frame configure:(SGTagsViewConfigure *)configure; 95 | /** 类方法 */ 96 | + (instancetype)tagsViewWithFrame:(CGRect)frame configure:(SGTagsViewConfigure *)configure; 97 | /** 标签数组 */ 98 | @property (nonatomic, strong) NSArray *tags; 99 | /** 根据下标数组值选取对应的标签 100 | * 主要用于初始化 SGTagsView 时,默认选中的标签,multipleSelect = NO 时,仅支持最后一个值所对应的标签 */ 101 | @property (nonatomic, strong) NSArray *tagIndexs; 102 | /** 是否固定 SGTagsView 初始 frame 的高度,默认为 NO(不固定); 103 | * 设为 YES 时,内容超出初始 frame 的高度时,将会滚动 */ 104 | @property (nonatomic, assign) BOOL isFixedHeight; 105 | /** 均分垂直、垂直样式下标签布局完成后返回 SGTagsView 高度的回调函数 */ 106 | @property (nonatomic, copy) SGTagsViewHeightBlock heightBlock; 107 | /** 标签单选回调函数 */ 108 | @property (nonatomic, copy) SGTagsViewSingleSelectBlock singleSelectBlock; 109 | /** 标签多选回调函数 */ 110 | @property (nonatomic, copy) SGTagsViewMultipleSelectBlock multipleSelectBlock; 111 | 112 | /** 设置标签内部的小图标 */ 113 | - (void)setImageNames:(NSArray *)imageNames imagePositionStyle:(SGImagePositionStyle)imagePositionStyle spacing:(CGFloat)spacing; 114 | /** 根据标签下表设置标签内部的小图标 */ 115 | - (void)setImageName:(NSString *)imageName imagePositionStyle:(SGImagePositionStyle)imagePositionStyle spacing:(CGFloat)spacing forIndex:(NSInteger)index; 116 | 117 | @end 118 | -------------------------------------------------------------------------------- /Flower/Flower/Example/FileVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileVC.swift 3 | // FileVC 4 | // 5 | // Created by kingsic on 2021/12/31. 6 | // Copyright © 2021 kingsic. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class FileVC: UIViewController { 12 | 13 | lazy var dataSource = ["创建文件夹", "创建二级文件夹", "创建三级文件夹", "删除文件夹", "获取文件夹的大小", "保存数据", "获取数据", "添加新数据", "删除指定数据"] 14 | 15 | @IBOutlet weak var tableView: UITableView! 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | // Do any additional setup after loading the view. 21 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cellID") 22 | tableView.separatorStyle = .none 23 | } 24 | 25 | var dict: Dictionary = [ 26 | "array": [ 27 | [ 28 | "name": "Swift", 29 | "isSave": "false" 30 | ], 31 | [ 32 | "name": "Objective-C", 33 | "isSave": "true" 34 | ] 35 | ] 36 | ] 37 | 38 | } 39 | 40 | extension FileVC: UITableViewDelegate { 41 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 42 | if indexPath.row == 0 { 43 | print("测试单文件夹 - - \(FileTool.caches(name: "测试文件夹"))") 44 | } else if indexPath.row == 1 { 45 | print("测试双文件夹 - - \(FileTool.caches(name: "一级文件夹", subName: "二级文件夹"))") 46 | } else if indexPath.row == 2 { 47 | let filePath = FileTool.caches(name: "一级文件夹", subName: "二级文件夹", subSubName: "三级文件夹") 48 | print("filePath - - \(filePath)") 49 | } else if indexPath.row == 3 { 50 | let filePath = FileTool.caches(name: "一级文件夹", subName: "二级文件夹", subSubName: "三级文件夹") 51 | print("删除文件夹 - - \(FileTool.clean(name: filePath))") 52 | } else if indexPath.row == 4 { 53 | let filePath = FileTool.caches(name: "一级文件夹", subName: "二级文件夹", subSubName: "三级文件夹") 54 | let size = FileTool.size(name: filePath) 55 | print("文件夹大小 - - \(filePath) - - \(size) - - \(FileTool.conversion(size: UInt64(size)))") 56 | } else if indexPath.row == 5 { 57 | let filePath = FileTool.caches(name: "测试文件夹") 58 | let dataName = (filePath as NSString).appendingPathComponent("test.json") 59 | let result = (dict as NSDictionary).write(toFile: dataName, atomically: true) 60 | 61 | if result { 62 | print("数据保存成功") 63 | } else { 64 | print("数据保存失败") 65 | } 66 | print("fliePath - - \(filePath)") 67 | } else if indexPath.row == 6 { 68 | let filePath = FileTool.caches(name: "测试文件夹") 69 | let dataName = (filePath as NSString).appendingPathComponent("test.json") 70 | let dict = NSDictionary(contentsOfFile: dataName) 71 | print("data - - \(dict!)") 72 | } else if indexPath.row == 7 { 73 | var array: Array = dict["array"]! 74 | array.append(["name": "Flutter", "isSave": "false"]) 75 | array.insert(["name": "Java", "isSave": "true"], at: 0) 76 | dict["array"] = array 77 | let filePath = FileTool.caches(name: "测试文件夹") 78 | let dataName = (filePath as NSString).appendingPathComponent("test.json") 79 | let result = (dict as NSDictionary).write(toFile: dataName, atomically: true) 80 | 81 | if result { 82 | print("更新数据成功") 83 | } else { 84 | print("更新数据失败") 85 | } 86 | let dict = NSDictionary(contentsOfFile: dataName) 87 | print("data - - \(dict!)") 88 | } else if indexPath.row == 8 { 89 | var array: Array = dict["array"]! 90 | array.remove(at: 0) 91 | dict["array"] = array 92 | let filePath = FileTool.caches(name: "测试文件夹") 93 | let dataName = (filePath as NSString).appendingPathComponent("test.json") 94 | let result = (dict as NSDictionary).write(toFile: dataName, atomically: true) 95 | 96 | if result { 97 | print("删除数据成功") 98 | } else { 99 | print("删除数据失败") 100 | } 101 | let dict = NSDictionary(contentsOfFile: dataName) 102 | print("data - - \(dict!)") 103 | } 104 | } 105 | } 106 | 107 | extension FileVC: UITableViewDataSource { 108 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 109 | return dataSource.count 110 | } 111 | 112 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 113 | let cell = tableView.dequeueReusableCell(withIdentifier: "cellID", for: indexPath) 114 | cell.textLabel?.text = dataSource[indexPath.row] 115 | return cell 116 | } 117 | 118 | 119 | } 120 | -------------------------------------------------------------------------------- /Flower/Flower/Tools/FileTool.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileTool.swift 3 | // FileTool 4 | // 5 | // Created by kingsic on 2021/11/17. 6 | // Copyright © 2021 kingsic. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class FileTool: NSObject { 12 | /// 获取亦或是创建 Caches 文件夹下的文件夹 13 | /// 14 | /// - parameter name:文件夹名称 15 | public class func caches(name: String) -> String { 16 | let cachesPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first 17 | let filePath = "\(cachesPath!)/\(name)" 18 | let fileManager = FileManager.default 19 | if fileManager.fileExists(atPath: filePath, isDirectory: nil) { 20 | try! fileManager.createDirectory(atPath: filePath, withIntermediateDirectories: true, attributes: nil) 21 | return filePath 22 | } else { 23 | try! fileManager.createDirectory(atPath: filePath, withIntermediateDirectories: true, attributes: nil) 24 | return filePath 25 | } 26 | } 27 | 28 | /// 获取亦或是创建 Caches 文件夹下的文件夹下的文件夹 29 | /// 30 | /// - parameter name:文件夹名称 31 | /// 32 | /// - parameter subName:二级文件夹名称 33 | public class func caches(name: String, subName: String) -> String { 34 | let cachesPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first 35 | let filePath = "\(cachesPath!)/\(name)/\(subName)" 36 | let fileManager = FileManager.default 37 | if fileManager.fileExists(atPath: filePath, isDirectory: nil) { 38 | try! fileManager.createDirectory(atPath: filePath, withIntermediateDirectories: true, attributes: nil) 39 | return filePath 40 | } else { 41 | try! fileManager.createDirectory(atPath: filePath, withIntermediateDirectories: true, attributes: nil) 42 | return filePath 43 | } 44 | } 45 | 46 | /// 获取亦或是创建 Caches 文件夹下的文件夹下的文件夹 47 | /// 48 | /// - parameter name:文件夹名称 49 | /// 50 | /// - parameter subName:二级文件夹名称 51 | /// 52 | /// - parameter subSubName:三级文件夹名称 53 | public class func caches(name: String, subName: String, subSubName: String) -> String { 54 | let cachesPath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first 55 | let filePath = "\(cachesPath!)/\(name)/\(subName)/\(subSubName)" 56 | let fileManager = FileManager.default 57 | if fileManager.fileExists(atPath: filePath, isDirectory: nil) { 58 | try! fileManager.createDirectory(atPath: filePath, withIntermediateDirectories: true, attributes: nil) 59 | return filePath 60 | } else { 61 | try! fileManager.createDirectory(atPath: filePath, withIntermediateDirectories: true, attributes: nil) 62 | return filePath 63 | } 64 | } 65 | 66 | /// 移除文件夹 67 | public class func clean(name: String) { 68 | let fileManager = FileManager.default 69 | do { 70 | try fileManager.removeItem(atPath: name) 71 | } catch { 72 | 73 | } 74 | } 75 | 76 | /// 计算文件夹的大小 77 | public class func size(name: String) -> UInt64 { 78 | var size: UInt64 = 0 79 | let fileManager = FileManager.default 80 | var isDir: ObjCBool = false 81 | let isExists = fileManager.fileExists(atPath: name, isDirectory: &isDir) 82 | // 判断文件存在 83 | if isExists { 84 | // 是否为文件夹 85 | if isDir.boolValue { 86 | // 迭代器 存放文件夹下的所有文件名 87 | let enumerator = fileManager.enumerator(atPath: name) 88 | for subPath in enumerator! { 89 | // 获得全路径 90 | let fullPath = name.appending("/\(subPath)") 91 | do { 92 | let attr = try fileManager.attributesOfItem(atPath: fullPath) 93 | size += attr[FileAttributeKey.size] as! UInt64 94 | } catch { 95 | print("error :\(error)") 96 | } 97 | } 98 | } else { // 单文件 99 | do { 100 | let attr = try fileManager.attributesOfItem(atPath: name) 101 | size += attr[FileAttributeKey.size] as! UInt64 102 | } catch { 103 | print("error :\(error)") 104 | } 105 | } 106 | } 107 | return size 108 | } 109 | 110 | /// 文件夹大小格式转换 111 | public class func conversion(size: UInt64) -> String { 112 | if size < 1024 { 113 | return String(format: "%.2fB", Double(size)) 114 | } else if size >= 1024 && size < (1024 * 1024) { 115 | return String(format: "%.2fKB", Double(size / 1024)) 116 | } else if size >= (1024 * 1024) && size < (1024 * 1024 * 1024) { 117 | return String(format: "%.2fMB", Double(size / (1024 * 1024))) 118 | } else { 119 | return String(format: "%.2fGB", Double(size / (1024 * 1024 * 1024))) 120 | } 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /FlowerObjc/SourcesObjc/Tools/SensitiveWords/SGSensitiveWordsTool.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGSensitiveWordsTool 3 | // 4 | // Created by kingsic on 2017/3/16. 5 | // Copyright © 2017年 kingsic. All rights reserved. 6 | // 7 | 8 | #import "SGSensitiveWordsTool.h" 9 | #define EXIST @"isExists" 10 | 11 | @interface SGSensitiveWordsTool() 12 | @property (nonatomic,strong) NSMutableDictionary *root; 13 | @property(nonatomic,strong)NSMutableArray *rootArray; 14 | @property (nonatomic,assign) BOOL isFilterClose; 15 | @end 16 | 17 | @implementation SGSensitiveWordsTool 18 | 19 | static SGSensitiveWordsTool *instance; 20 | 21 | - (NSMutableArray *)rootArray { 22 | if (!_rootArray) { 23 | _rootArray = [NSMutableArray array]; 24 | } 25 | return _rootArray; 26 | } 27 | 28 | + (instancetype)shared { 29 | static dispatch_once_t onceToken; 30 | dispatch_once(&onceToken, ^{ 31 | instance = [[self alloc]init]; 32 | }); 33 | return instance; 34 | } 35 | 36 | // 复写init方法 37 | - (instancetype)init { 38 | if (self) { 39 | self = [super init]; 40 | // 加载本地文件 41 | NSString *filePath = [[NSBundle mainBundle] pathForResource:@"SensitiveWords" ofType:@"txt"]; 42 | [self initFilter:filePath]; 43 | } 44 | return self; 45 | } 46 | 47 | /** 48 | * 加载本地的敏感词库 49 | * 50 | * @param filepath 敏感词文件的路径 51 | */ 52 | - (void)initFilter:(NSString *)filepath { 53 | 54 | self.root = [NSMutableDictionary dictionary]; 55 | 56 | NSString *fileString = [[NSString alloc] initWithContentsOfFile:filepath encoding:NSUTF8StringEncoding error:nil]; 57 | 58 | [self.rootArray removeAllObjects]; 59 | [self.rootArray addObjectsFromArray:[fileString componentsSeparatedByString:@"|"]]; 60 | 61 | for (NSString *str in self.rootArray) { 62 | // 插入字符,构造节点 63 | [self insertWords:str]; 64 | } 65 | } 66 | 67 | /** 68 | * 判断文本中是否含有敏感词 69 | * 70 | * @param string 文本字符串 71 | * 72 | * @return 是否含有敏感词 73 | */ 74 | - (BOOL)includeSensitiveWords:(NSString *)string { 75 | 76 | if (self.isFilterClose || !self.root) { 77 | return NO; 78 | } 79 | 80 | NSMutableString *result = result = [string mutableCopy]; 81 | 82 | for (int i = 0; i < string.length; i++) { 83 | NSString *subString = [string substringFromIndex: i]; 84 | NSMutableDictionary *node = [self.root mutableCopy] ; 85 | int num = 0; 86 | 87 | for (int j = 0; j < subString.length; j++) { 88 | NSString *word = [subString substringWithRange:NSMakeRange(j, 1)]; 89 | 90 | if (node[word] == nil) { 91 | break; 92 | } else { 93 | num ++; 94 | node = node[word]; 95 | } 96 | 97 | // 敏感词匹配成功 98 | if ([node[EXIST]integerValue] == 1) { 99 | return YES; 100 | } 101 | } 102 | } 103 | return NO; 104 | } 105 | 106 | 107 | - (void)insertWords:(NSString *)words { 108 | NSMutableDictionary *node = self.root; 109 | for (int i = 0; i < words.length; i ++) { 110 | NSString *word = [words substringWithRange:NSMakeRange(i, 1)]; 111 | if (node[word] == nil) { 112 | node[word] = [NSMutableDictionary dictionary]; 113 | } 114 | node = node[word]; 115 | } 116 | // 敏感词最后一个字符标识 117 | node[EXIST] = [NSNumber numberWithInt:1]; 118 | } 119 | 120 | /** 121 | * 将文本中含有的敏感词进行替换 122 | * 123 | * @param string 文本字符串 124 | * @param tempWord 替换的字符 125 | * 126 | * @return 过滤完敏感词之后的文本 127 | */ 128 | - (NSString *)replaceSensitiveWords:(NSString *)string withWord:(NSString *)tempWord { 129 | 130 | if (self.isFilterClose || !self.root) { 131 | return string; 132 | } 133 | 134 | NSMutableString *result = result = [string mutableCopy]; 135 | 136 | for (int i = 0; i < string.length; i ++) { 137 | NSString *subString = [string substringFromIndex: i]; 138 | NSMutableDictionary *node = [self.root mutableCopy] ; 139 | int num = 0; 140 | 141 | for (int j = 0; j < subString.length; j ++) { 142 | NSString *word = [subString substringWithRange:NSMakeRange(j, 1)]; 143 | 144 | if (node[word] == nil) { 145 | break; 146 | } else { 147 | num ++; 148 | node = node[word]; 149 | } 150 | 151 | // 敏感词匹配成功 152 | if ([node[EXIST]integerValue] == 1) { 153 | NSMutableString *symbolStr = [NSMutableString string]; 154 | for (int k = 0; k < num; k ++) { 155 | [symbolStr appendString:tempWord]; 156 | } 157 | [result replaceCharactersInRange:NSMakeRange(i, num) withString:symbolStr]; 158 | i += j; 159 | break; 160 | } 161 | } 162 | } 163 | return result; 164 | } 165 | 166 | - (void)freeFilter { 167 | self.root = nil; 168 | } 169 | 170 | - (void)stopFilter:(BOOL)b { 171 | self.isFilterClose = b; 172 | } 173 | 174 | 175 | @end 176 | -------------------------------------------------------------------------------- /FlowerObjc/SourcesObjc/Tools/SensitiveWords/SensitiveWords.txt: -------------------------------------------------------------------------------- 1 | 湿|性|艳|乳|裆|胯|奶|淫|欲望|黄色|叫床|绳虐|精液|情欲|色欲|爱液|高潮|肛交|阴水|阴茎|阴蒂|阴道|阴唇|射精|鸡巴|色情|烂逼|卖逼|贱逼|兽交|鸡奸|诱奸|生殖器|阳具|操逼|法轮功|李洪志|明慧新闻|胡锦涛|江泽民|毛泽东|邓小平|习近平|共产党|中共|台独|台湾独立|藏独|西藏独立|疆独|新疆独立|天安门屠杀|发抡功|九评|起点网|起点中文|逐浪网|世纪文学|幻剑书盟|天下书盟|红薯|纵横网|纵横中文|红袖添香|晋江|潇湘|旗峰|自慰|A片|A级|三级|裸体|胴体|失身|私处|强奸|轮奸|群奸|SM|情色|色图|色诱|一夜情|夜激情|禁片|爱液|G点|波波|肛交|裸女|3级片|三级片|四级片|4级片|日日|我日|我操|傻逼|色情|骚货|日死你|日你|去你妈的|屁眼|卖比|卖逼|烂比|烂逼|狂操|靠你妈|贱人|贱货|贱逼|奸淫|狗操|狗b|二逼|二B|大花逼|大逼|操他|操你妈|操你|吮吸|肛|虐待|奸尸|虐猫|虐畜|兽交|鸡奸|诱奸|乱伦|处女膜|荡妇|发骚|骚妇|骚妹|AV|女优|妓女|发浪|破处|生殖器官|射精|受孕|淫靡|开苞|芳穴|蜜液|小鸡鸡|JJ|花蕊|猥亵|做爱|风骚|裸戏|罩杯|贞操|玉体|泄春光|波霸|裸体|自摸|私处|丰胸翘臀|赤裸|裸体|巨波|波波|双沟|臀沟|酥胸|罩杯|凸点|乱伦|闷骚|胴体|酥胸|欲火|咪咪|燥热|骚妖|媚妙|波霸|色诱|色情|骚货|呻吟|消魂夺魄|赤裸裸|裸|奸|娇喘|欲仙欲死|强暴|gay|逼样|婊子|操逼|操比|操蛋|大b|大B|干你娘|干死你|贱比|杂种|招妓|作爱|玉体|鸦片|冰毒|吗啡|摇头丸|注射毒品|海洛因|走私|百家乐|廿一点|鱼虾蟹骰子|骰宝赌大|骰宝赌小|富贵三公|三公百家乐|轮盘|廿五点|联奖扑克|牌九|麻将牌九|雀九|番摊|角子机|白鸽票|泵波拿|博彩赛狗|赛马|大法|法轮|法轮功|法轮大法|人民报|真善忍|转法轮|自焚|一党|多党|民主|专政|民运|六四|暴动|暴乱|发抡|发伦|发伦功|发论|发论公|发论功|法lun功|法功|法愣|法仑|法伦|法论|法十轮十功|法谪|抡功|伦功|轮大|氵去|氵去车仑工力|人兽|2P|3P|群P|女优|肉缝|肉棒|花间|风色|推倒|勾搭|坚挺|双峰|阿扁推翻|阿宾|阿賓|挨了一炮|爱液横流|安街逆|安局办公楼|安局豪华|安门事|安眠藥|案的准确|八九民|八九学|八九政治|把病人整|把邓小平|把学生整|罢工门|白黄牙签|败培训|办本科|办理本科|办理各种|办理票据|办理文凭|办理真实|办理证书|办理资格|办文凭|办怔|办证|半刺刀|辦毕业|辦證|谤罪获刑|磅解码器|磅遥控器|宝在甘肃修|保过答案|报复执法|爆发骚|北省委门|被打死|被指抄袭|被中共|本公司担|本无码|毕业證|变牌绝|辩词与梦|冰毒|冰火毒|冰火佳|冰火九重|冰火漫|冰淫传|冰在火上|波推龙|博彩娱|博会暂停|博园区伪|不查都|不查全|不思四化|布卖淫女|部忙组阁|部是这样|才知道只生|财众科技|采花堂|踩踏事|苍山兰|苍蝇水|藏春阁|藏獨|操了嫂|操嫂子|策没有不|插屁屁|察象蚂|拆迁灭|车牌隐|成人电|成人卡通|成人聊|成人片|成人视|成人图|成人文|成人小|城管灭|惩公安|惩贪难|充气娃|冲凉死|抽着大中|抽着芙蓉|出成绩付|出售发票|出售军|穿透仪器|春水横溢|纯度白|纯度黄|次通过考|催眠水|催情粉|催情药|催情藥|挫仑|达毕业证|答案包|答案提供|打飞机专|打死经过|打死人|打砸办公|大鸡巴|大雞巴|大纪元|大揭露|大奶子|大批贪官|大肉棒|大嘴歌|代办发票|代办各|代办文|代办学|代办制|代辦|代表烦|代理发票|代理票据|代您考|代您考|代写毕|代写论|代孕|贷办|贷借款|贷开|戴海静|当代七整|当官要精|当官在于|党的官|党后萎|党前干劲|刀架保安|导的情人|导叫失|导人的最|导人最|导小商|到花心|得财兼|的同修|灯草和|等级證|等屁民|等人老百|等人是老|等人手术|邓爷爷转|邓玉娇|地产之歌|地下先烈|地震哥|帝国之梦|递纸死|点数优惠|电狗|电话监|电鸡|甸果敢|蝶舞按|丁香社|丁子霖|顶花心|东北独立|东复活|东京热|東京熱|洞小口紧|都当警|都当小姐|都进中央|毒蛇钻|独立台湾|赌球网|短信截|对日强硬|多美康|躲猫猫|俄羅斯|恶势力操|恶势力插|恩氟烷|儿园惨|儿园砍|儿园杀|儿园凶|二奶大|发牌绝|发票出|发票代|发票销|發票|法车仑|法伦功|法轮|法轮佛|法维权|法一轮|法院给废|法正乾|反测速雷|反雷达测|反屏蔽|范燕琼|方迷香|防电子眼|防身药水|房贷给废|仿真枪|仿真证|诽谤罪|费私服|封锁消|佛同修|夫妻交换|福尔马林|福娃的預|福娃頭上|福香巴|府包庇|府集中领|妇销魂|附送枪|复印件生|复印件制|富民穷|富婆给废|改号软件|感扑克|冈本真|肛交|肛门是邻|岡本真|钢针狗|钢珠枪|港澳博球|港馬會|港鑫華|高就在政|高考黑|高莺莺|搞媛交|告长期|告洋状|格证考试|各类考试|各类文凭|跟踪器|工程吞得|工力人|公安错打|公安网监|公开小姐|攻官小姐|共狗|共王储|狗粮|狗屁专家|鼓动一些|乖乖粉|官商勾|官也不容|官因发帖|光学真题|跪真相|滚圆大乳|国际投注|国家妓|国家软弱|国家吞得|国库折|国一九五七|國內美|哈药直销|海访民|豪圈钱|号屏蔽器|和狗交|和狗性|和狗做|黑火药的|红色恐怖|红外透视|紅色恐|胡江内斗|胡紧套|胡錦濤|胡适眼|胡耀邦|湖淫娘|虎头猎|华国锋|华门开|化学扫盲|划老公|还会吹萧|还看锦涛|环球证件|换妻|皇冠投注|黄冰|浑圆豪乳|活不起|火车也疯|机定位器|机号定|机号卫|机卡密|机屏蔽器|基本靠吼|绩过后付|激情电|激情短|激情妹|激情炮|级办理|级答案|急需嫖|集体打砸|集体腐|挤乳汁|擠乳汁|佳静安定|家一样饱|家属被打|甲虫跳|甲流了|奸成瘾|兼职上门|监听器|监听王|简易炸|江胡内斗|江太上|江系人|江贼民|疆獨|蒋彦永|叫自慰|揭贪难|姐包夜|姐服务|姐兼职|姐上门|金扎金|金钟气|津大地震|津地震|进来的罪|京地震|京要地震|经典谎言|精子射在|警察被|警察的幌|警察殴打|警察说保|警车雷达|警方包庇|警用品|径步枪|敬请忍|究生答案|九龙论坛|九评共|酒象喝汤|酒像喝汤|就爱插|就要色|举国体|巨乳|据说全民|绝食声|军长发威|军刺|军品特|军用手|开邓选|开锁工具|開碼|開票|砍杀幼|砍伤儿|康没有不|康跳楼|考答案|考后付款|考机构|考考邓|考联盟|考前答|考前答案|考前付|考设备|考试包过|考试保|考试答案|考试机构|考试联盟|考试枪|考研考中|考中答案|磕彰|克分析|克千术|克透视|空和雅典|孔摄像|控诉世博|控制媒|口手枪|骷髅死|快速办|矿难不公|拉登说|拉开水晶|来福猎|拦截器|狼全部跪|浪穴|老虎机|雷人女官|类准确答|黎阳平|李洪志|李咏曰|理各种证|理是影帝|理证件|理做帐报|力骗中央|力月西|丽媛离|利他林|连发手|聯繫電|炼大法|两岸才子|两会代|两会又三|聊视频|聊斋艳|了件渔袍|猎好帮手|猎枪销|猎槍|獵槍|领土拿|流血事|六合彩|六死|六四事|六月联盟|龙湾事件|隆手指|陆封锁|陆同修|氯胺酮|乱奸|乱伦类|乱伦小|亂倫|伦理大|伦理电影|伦理毛|伦理片|轮功|轮手枪|论文代|罗斯小姐|裸聊网|裸舞视|落霞缀|麻古|麻果配|麻果丸|麻将透|麻醉狗|麻醉枪|麻醉槍|麻醉藥|蟆叫专家|卖地财政|卖发票|卖银行卡|卖自考|漫步丝|忙爱国|猫眼工具|毛一鲜|媒体封锁|每周一死|美艳少妇|妹按摩|妹上门|门按摩|门保健|門服務|氓培训|蒙汗药|迷幻型|迷幻药|迷幻藥|迷昏口|迷昏药|迷昏藥|迷魂香|迷魂药|迷魂藥|迷奸药|迷情水|迷情药|迷藥|谜奸药|蜜穴|灭绝罪|民储害|民九亿商|民抗议|明慧网|铭记印尼|摩小姐|母乳家|木齐针|幕没有不|幕前戲|内射|南充针|嫩穴|嫩阴|泥马之歌|你的西域|拟涛哥|娘两腿之间|妞上门|浓精|怒的志愿|女被人家搞|女激情|女技师|女人和狗|女任职名|女上门|女優|鸥之歌|拍肩神药|拍肩型|牌分析|牌技网|炮的小蜜|陪考枪|配有消|喷尿|嫖俄罗|嫖鸡|平惨案|平叫到床|仆不怕饮|普通嘌|期货配|奇迹的黄|奇淫散|骑单车出|气狗|气枪|汽狗|汽枪|氣槍|铅弹|钱三字经|枪出售|枪的参|枪的分|枪的结|枪的制|枪货到|枪决女犯|枪决现场|枪模|枪手队|枪手网|枪销售|枪械制|枪子弹|强权政府|强硬发言|抢其火炬|切听器|窃听器|禽流感了|勤捞致|氢弹手|清除负面|清純壆|情聊天室|情妹妹|情视频|情自拍|氰化钾|氰化钠|请集会|请示威|请愿|琼花问|区的雷人|娶韩国|全真证|群奸暴|群起抗暴|群体性事|绕过封锁|惹的国|人权律|人体艺|人游行|人在云上|人真钱|认牌绝|任于斯国|柔胸粉|肉洞|肉棍|如厕死|乳交|软弱的国|赛后骚|三挫|三级片|三秒倒|三网友|三唑|骚妇|骚浪|骚穴|骚嘴|扫了爷爷|色电影|色妹妹|色视频|色小说|杀指南|山涉黑|煽动不明|煽动群众|上门激|烧公安局|烧瓶的|韶关斗|韶关玩|韶关旭|射网枪|涉嫌抄袭|深喉冰|神七假|神韵艺术|生被砍|生踩踏|生肖中特|圣战不息|盛行在舞|尸博|失身水|失意药|狮子旗|十八等|十大谎|十大禁|十个预言|十类人不|十七大幕|实毕业证|实体娃|实学历文|士康事件|式粉推|视解密|是躲猫|手变牌|手答案|手狗|手机跟|手机监|手机窃|手机追|手拉鸡|手木仓|手槍|守所死法|兽交|售步枪|售纯度|售单管|售弹簧刀|售防身|售狗子|售虎头|售火药|售假币|售健卫|售军用|售猎枪|售氯胺|售麻醉|售冒名|售枪支|售热武|售三棱|售手枪|售五四|售信用|售一元硬|售子弹|售左轮|书办理|熟妇|术牌具|双管立|双管平|水阎王|丝护士|丝情侣|丝袜保|丝袜恋|丝袜美|丝袜妹|丝袜网|丝足按|司长期有|司法黑|私房写真|死法分布|死要见毛|四博会|四大扯个|四小码|苏家屯集|诉讼集团|素女心|速代办|速取证|酸羟亚胺|蹋纳税|太王四神|泰兴幼|泰兴镇中|泰州幼|贪官也辛|探测狗|涛共产|涛一样胡|特工资|特码|特上门|体透视镜|替考|替人体|天朝特|天鹅之旅|天推广歌|田罢工|田田桑|田停工|庭保养|庭审直播|通钢总经|偷電器|偷肃贪|偷听器|偷偷贪|头双管|透视功能|透视镜|透视扑|透视器|透视眼镜|透视药|透视仪|秃鹰汽|突破封锁|突破网路|推油按|脱衣艳|瓦斯手|袜按摩|外透视镜|外围赌球|湾版假|万能钥匙|万人骚动|王立军|王益案|网民案|网民获刑|网民诬|微型摄像|围攻警|围攻上海|维汉员|维权基|维权人|维权谈|委坐船|谓的和谐|温家堡|温切斯特|温影帝|溫家寶|瘟加饱|瘟假饱|文凭证|文强|纹了毛|闻被控制|闻封锁|瓮安|我的西域|我搞台独|乌蝇水|无耻语录|无码专|五套功|五月天|午夜电|午夜极|武警暴|武警殴|武警已增|务员答案|务员考试|雾型迷|西藏限|西服进去|希脏|习进平|习晋平|席复活|席临终前|席指着护|洗澡死|喜贪赃|先烈纷纷|现大地震|现金投注|线透视镜|限制言|陷害案|陷害罪|相自首|香港论坛|香港马会|香港一类|香港总彩|硝化甘|小穴|校骚乱|协晃悠|写两会|泄漏的内|新建户|新疆叛|新疆限|新金瓶|新唐人|信访专班|信接收器|兴中心幼|星上门|行长王益|形透视镜|型手枪|姓忽悠|幸运码|性爱日|性福情|性感少|性推广歌|胸主席|徐玉元|学骚乱|学位證|學生妹|丫与王益|烟感器|严晓玲|言被劳教|言论罪|盐酸曲|颜射|恙虫病|姚明进去|要人权|要射精了|要射了|要泄了|夜激情|液体炸|一小撮别|遗情书|蚁力神|益关注组|益受贿|阴间来电|陰唇|陰道|陰戶|淫魔舞|淫情女|淫肉|淫騷妹|淫兽|淫兽学|淫水|淫穴|隐形耳|隐形喷剂|应子弹|婴儿命|咏妓|用手枪|幽谷三|游精佑|有奶不一|右转是政|幼齿类|娱乐透视|愚民同|愚民政|与狗性|玉蒲团|育部女官|冤民大|鸳鸯洗|园惨案|园发生砍|园砍杀|园凶杀|园血案|原一九五七|原装弹|袁腾飞|晕倒型|韵徐娘|遭便衣|遭到警|遭警察|遭武警|择油录|曾道人|炸弹教|炸弹遥控|炸广州|炸立交|炸药的制|炸药配|炸药制|张春桥|找枪手|找援交|找政法委副|赵紫阳|针刺案|针刺伤|针刺事|针刺死|侦探设备|真钱斗地|真钱投注|真善忍|真实文凭|真实资格|震惊一个民|震其国土|证到付款|证件办|证件集团|证生成器|证书办|证一次性|政府操|政论区|證件|植物冰|殖器护|指纹考勤|指纹膜|指纹套|至国家高|志不愿跟|制服诱|制手枪|制证定金|制作证件|中的班禅|中共黑|中国不强|种公务员|种学历证|众像羔|州惨案|州大批贪|州三箭|宙最高法|昼将近|主席忏|住英国房|助考|助考网|专业办理|专业代|专业代写|专业助|转是政府|赚钱资料|装弹甲|装枪套|装消音|着护士的胸|着涛哥|姿不对死|资格證|资料泄|梓健特药|字牌汽|自己找枪|自慰用|自由圣|自由亚|总会美女|足球玩法|最牛公安|醉钢枪|醉迷药|醉乙醚|尊爵粉|左转是政|作弊器|作各种证|作硝化甘|唑仑|做爱小|做原子弹|做证件 -------------------------------------------------------------------------------- /Flower/Flower/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /FlowerObjc/FlowerObjc/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /FlowerObjc/SourcesObjc/UIKit/SGItemsView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGItemsView.m 3 | // SGItemsView 4 | // 5 | // Created by kingsic on 2020/10/15. 6 | // Copyright © 2020 kingsic. All rights reserved. 7 | // 8 | 9 | #import "SGItemsView.h" 10 | 11 | @interface SGItemCell : UICollectionViewCell 12 | @property (nonatomic, strong) UIImageView *imgView; 13 | @property (nonatomic, strong) UILabel *titleLab; 14 | @end 15 | @implementation SGItemCell 16 | 17 | - (instancetype)initWithFrame:(CGRect)frame { 18 | if (self = [super initWithFrame:frame]) { 19 | self.backgroundColor = [UIColor clearColor]; 20 | [self addSubview:self.imgView]; 21 | [self addSubview:self.titleLab]; 22 | } 23 | return self; 24 | } 25 | - (void)layoutSubviews { 26 | [super layoutSubviews]; 27 | 28 | CGFloat iv_y = 5; 29 | CGFloat iv_height = self.frame.size.height / 3 * 2 - iv_y; 30 | CGFloat iv_width = iv_height; 31 | CGFloat iv_x = 0.5 * (self.frame.size.width - iv_width); 32 | _imgView.frame = CGRectMake(iv_x, iv_y, iv_width, iv_height); 33 | 34 | CGFloat tl_x = 0; 35 | CGFloat tl_y = self.frame.size.height / 3 * 2; 36 | CGFloat tl_width = self.frame.size.width; 37 | CGFloat tl_height = self.frame.size.height / 3; 38 | _titleLab.frame = CGRectMake(tl_x, tl_y, tl_width, tl_height); 39 | } 40 | 41 | - (UIImageView *)imgView { 42 | if (!_imgView) { 43 | _imgView = [[UIImageView alloc] init]; 44 | } 45 | return _imgView; 46 | } 47 | - (UILabel *)titleLab { 48 | if (!_titleLab) { 49 | _titleLab = [[UILabel alloc] init]; 50 | _titleLab.textAlignment = NSTextAlignmentCenter; 51 | } 52 | return _titleLab; 53 | } 54 | @end 55 | 56 | 57 | @interface SGItemsView () 58 | @property (nonatomic, strong) UICollectionViewFlowLayout *CVFlowLayout; 59 | @property (nonatomic, strong) UICollectionView *collectionView; 60 | @property (nonatomic, strong) NSMutableDictionary *itemTitleColorMDict; 61 | @end 62 | 63 | @implementation SGItemsView 64 | 65 | - (instancetype)initWithFrame:(CGRect)frame { 66 | if (self = [super initWithFrame:frame]) { 67 | [self configure]; 68 | [self addSubview:self.collectionView]; 69 | } 70 | return self; 71 | } 72 | - (void)configure { 73 | _titleFont = [UIFont systemFontOfSize:12]; 74 | } 75 | 76 | - (void)layoutSubviews { 77 | [super layoutSubviews]; 78 | 79 | _CVFlowLayout.itemSize = CGSizeMake(_itemSize.width, _itemSize.height); 80 | 81 | CGFloat cv_y = 0; 82 | CGFloat cv_width = self.frame.size.width; 83 | CGFloat cv_height = self.frame.size.height; 84 | _collectionView.frame = CGRectMake(0, cv_y, cv_width, cv_height); 85 | } 86 | 87 | - (UICollectionView *)collectionView { 88 | if (!_collectionView) { 89 | _CVFlowLayout = [[UICollectionViewFlowLayout alloc] init]; 90 | _CVFlowLayout.minimumInteritemSpacing = 0; 91 | _CVFlowLayout.minimumLineSpacing = 0; 92 | 93 | _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:_CVFlowLayout]; 94 | _collectionView.delegate = self; 95 | _collectionView.dataSource = self; 96 | [_collectionView registerClass:[SGItemCell class] forCellWithReuseIdentifier:@"cellID"]; 97 | _collectionView.backgroundColor = self.backgroundColor; 98 | _collectionView.showsHorizontalScrollIndicator = _showsHorizontalScrollIndicator; 99 | } 100 | return _collectionView; 101 | } 102 | #pragma mark - - UICollectionViewDataSource 103 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 104 | return _titles.count; 105 | } 106 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 107 | SGItemCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cellID" forIndexPath:indexPath]; 108 | if (self.configureImgViewBlock) { 109 | self.configureImgViewBlock(cell.imgView, indexPath.row); 110 | } 111 | 112 | cell.titleLab.text = _titles[indexPath.item]; 113 | if (_titleFont) { 114 | cell.titleLab.font = _titleFont; 115 | } 116 | if (_titleColor) { 117 | cell.titleLab.textColor = _titleColor; 118 | } 119 | 120 | if (_itemTitleColorMDict) { 121 | [self.itemTitleColorMDict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { 122 | NSInteger index = [key integerValue]; 123 | if (index == indexPath.row) { 124 | cell.titleLab.textColor = obj; 125 | } 126 | }]; 127 | } 128 | return cell; 129 | } 130 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { 131 | if (self.itemClickBlock) { 132 | self.itemClickBlock(indexPath.row); 133 | } 134 | } 135 | 136 | #pragma mark - - set 137 | - (void)setTitles:(NSArray *)titles { 138 | _titles = titles; 139 | [_collectionView reloadData]; 140 | } 141 | - (void)setTitleFont:(UIFont *)titleFont { 142 | _titleFont = titleFont; 143 | } 144 | - (void)setTitleColor:(UIColor *)titleColor { 145 | _titleColor = titleColor; 146 | } 147 | 148 | - (void)setItemSize:(CGSize)itemSize { 149 | _itemSize = itemSize; 150 | } 151 | - (void)setContentInset:(UIEdgeInsets)contentInset { 152 | _collectionView.contentInset = UIEdgeInsetsMake(contentInset.top, contentInset.left, contentInset.bottom, contentInset.right); 153 | } 154 | - (void)setPagingEnabled:(BOOL)pagingEnabled { 155 | _collectionView.pagingEnabled = pagingEnabled; 156 | } 157 | - (void)setScrollDirectionHorizontal:(BOOL)scrollDirectionHorizontal { 158 | if (scrollDirectionHorizontal == YES) { 159 | _CVFlowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 160 | } 161 | } 162 | - (void)setShowsHorizontalScrollIndicator:(BOOL)showsHorizontalScrollIndicator { 163 | _collectionView.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator; 164 | } 165 | - (void)setShowsVerticalScrollIndicator:(BOOL)showsVerticalScrollIndicator { 166 | _collectionView.showsVerticalScrollIndicator = showsVerticalScrollIndicator; 167 | } 168 | 169 | - (void)setItemTitleColor:(UIColor *)titleColor forIndex:(NSInteger)index { 170 | [self.itemTitleColorMDict setObject:titleColor forKey:@(index)]; 171 | } 172 | - (NSMutableDictionary *)itemTitleColorMDict { 173 | if (!_itemTitleColorMDict) { 174 | _itemTitleColorMDict = [NSMutableDictionary dictionary]; 175 | } 176 | return _itemTitleColorMDict; 177 | } 178 | 179 | @end 180 | -------------------------------------------------------------------------------- /FlowerObjc/SourcesObjc/UIKit/SGGuidePageView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGGuidePageView.m 3 | // SGGuidePageView 4 | // 5 | // Created by kingsic on 2017/8/7. 6 | // Copyright © 2017 kingsic. All rights reserved. 7 | // 8 | 9 | #import "SGGuidePageView.h" 10 | 11 | @interface SGGuidePageView () 12 | @property (nonatomic, strong) UIScrollView *contentScrollView; 13 | @property (nonatomic, strong) UIPageControl *pageControl; 14 | @property (nonatomic, strong) NSArray *image_array; 15 | @end 16 | 17 | @implementation SGGuidePageView 18 | 19 | - (instancetype)initWithFrame:(CGRect)frame { 20 | if (self = [super initWithFrame:frame]) { 21 | [self initialization]; 22 | [self addSubviews]; 23 | } 24 | return self; 25 | } 26 | 27 | - (void)initialization { 28 | _durationTime = 0.25; 29 | _pageControlOffsetY = 0.87; 30 | _pageControlType = SGPageControlTypeNone; 31 | _pageIndicatorTintColor = [UIColor grayColor]; 32 | _currentPageIndicatorTintColor = [UIColor whiteColor]; 33 | } 34 | 35 | - (void)addSubviews { 36 | [self addSubview:self.contentScrollView]; 37 | } 38 | 39 | - (UIScrollView *)contentScrollView { 40 | if (!_contentScrollView) { 41 | _contentScrollView = [[UIScrollView alloc] init]; 42 | _contentScrollView.delegate = self; 43 | _contentScrollView.bounces = NO; 44 | _contentScrollView.pagingEnabled = YES; 45 | _contentScrollView.showsVerticalScrollIndicator = NO; 46 | _contentScrollView.showsHorizontalScrollIndicator = NO; 47 | _contentScrollView.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height); 48 | } 49 | return _contentScrollView; 50 | } 51 | 52 | - (void)setImages:(NSArray *)images { 53 | _images = images; 54 | self.image_array = [NSArray arrayWithArray:images]; 55 | 56 | CGFloat selfWidth = self.frame.size.width; 57 | CGFloat selfHeight = self.frame.size.height; 58 | self.contentScrollView.contentSize = CGSizeMake(selfWidth * images.count, selfHeight); 59 | 60 | for (NSInteger i = 0; i < images.count; i++) { 61 | UIImageView *imageView = [[UIImageView alloc] init]; 62 | imageView.frame = CGRectMake(selfWidth * i, 0, selfWidth, selfHeight); 63 | imageView.image = [UIImage imageNamed:images[i]]; 64 | [self.contentScrollView addSubview:imageView]; 65 | 66 | if (i == images.count - 1) { 67 | imageView.userInteractionEnabled = YES; 68 | [imageView addSubview:self.enterBtn]; 69 | } 70 | } 71 | } 72 | 73 | - (UIButton *)enterBtn { 74 | if (!_enterBtn) { 75 | _enterBtn = [UIButton buttonWithType:(UIButtonTypeCustom)]; 76 | CGFloat width = 100; 77 | CGFloat height = 50; 78 | CGFloat x = 0.5 * (self.frame.size.width - width); 79 | CGFloat y = self.frame.size.height * 0.87 - 15; 80 | _enterBtn.frame = CGRectMake(x, y, width, height); 81 | [_enterBtn setTitle:@"立即体验" forState:(UIControlStateNormal)]; 82 | [_enterBtn addTarget:self action:@selector(enterBtn_action) forControlEvents:(UIControlEventTouchUpInside)]; 83 | } 84 | return _enterBtn; 85 | } 86 | 87 | - (void)enterBtn_action { 88 | [UIView animateWithDuration:self.durationTime animations:^{ 89 | self.alpha = 0.0; 90 | } completion:^(BOOL finished) { 91 | [self removeFromSuperview]; 92 | }]; 93 | } 94 | 95 | - (void)setDurationTime:(CGFloat)durationTime { 96 | _durationTime = durationTime; 97 | } 98 | 99 | - (void)setPageControlType:(SGPageControlType)pageControlType { 100 | _pageControlType = pageControlType; 101 | if (pageControlType == SGPageControlTypeAlways) { 102 | [self addSubview:self.pageControl]; 103 | } else if (pageControlType == SGPageControlTypeDisappear) { 104 | [self addSubview:self.pageControl]; 105 | } 106 | } 107 | 108 | - (void)setPageIndicatorTintColor:(UIColor *)pageIndicatorTintColor { 109 | _pageIndicatorTintColor = pageIndicatorTintColor; 110 | if (_pageControl != nil) { 111 | _pageControl.pageIndicatorTintColor = pageIndicatorTintColor; 112 | } 113 | } 114 | - (void)setCurrentPageIndicatorTintColor:(UIColor *)currentPageIndicatorTintColor { 115 | _currentPageIndicatorTintColor = currentPageIndicatorTintColor; 116 | if (_pageControl != nil) { 117 | _currentPageIndicatorTintColor = currentPageIndicatorTintColor; 118 | } 119 | } 120 | 121 | - (void)setLastPageDisapper:(BOOL)lastPageDisapper { 122 | _lastPageDisapper = lastPageDisapper; 123 | } 124 | 125 | - (void)setPageControlOffsetY:(CGFloat)pageControlOffsetY { 126 | _pageControlOffsetY = pageControlOffsetY; 127 | if (_pageControl != nil) { 128 | CGRect frame = _pageControl.frame; 129 | frame.origin.y = pageControlOffsetY * self.frame.size.height; 130 | _pageControl.frame = frame; 131 | } 132 | } 133 | 134 | - (UIPageControl *)pageControl { 135 | if (!_pageControl) { 136 | _pageControl = [[UIPageControl alloc] init]; 137 | _pageControl.frame = CGRectMake(0, self.frame.size.height * _pageControlOffsetY, self.frame.size.width, 30); 138 | _pageControl.currentPage = 0; 139 | _pageControl.userInteractionEnabled = NO; 140 | _pageControl.numberOfPages = self.image_array.count; 141 | _pageControl.pageIndicatorTintColor = _pageIndicatorTintColor; 142 | _pageControl.currentPageIndicatorTintColor = _currentPageIndicatorTintColor; 143 | } 144 | return _pageControl; 145 | } 146 | 147 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { 148 | if (_pageControlType == SGPageControlTypeAlways) { 149 | self.pageControl.currentPage = ((scrollView.contentOffset.x / scrollView.frame.size.width) + 0.5f); 150 | } else if (_pageControlType == SGPageControlTypeDisappear) { 151 | CGFloat offsetX = scrollView.contentOffset.x / scrollView.frame.size.width; 152 | NSInteger index = offsetX + 0.5f; 153 | self.pageControl.currentPage = index; 154 | 155 | if (self.image_array.count - 2 < offsetX < self.image_array.count - 1 ) { 156 | CGFloat alpha = 1 - 2 * (offsetX - (self.image_array.count - 2)); 157 | self.pageControl.alpha = alpha; 158 | } 159 | } 160 | } 161 | 162 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { 163 | if (_lastPageDisapper == YES) { 164 | int cuttentIndex = (int)(scrollView.contentOffset.x + 0.5 * self.frame.size.width)/self.frame.size.width; 165 | if (cuttentIndex == self.image_array.count - 1) { 166 | if ([self isScrolltoLeft:scrollView]) { 167 | [self enterBtn_action]; 168 | } 169 | } 170 | } 171 | } 172 | - (BOOL )isScrolltoLeft:(UIScrollView *)scrollView { 173 | if ([scrollView.panGestureRecognizer translationInView:scrollView.superview].x < 0) { 174 | return YES; 175 | } else { 176 | return NO; 177 | } 178 | } 179 | 180 | @end 181 | -------------------------------------------------------------------------------- /Flower/Flower/Example/PopupMenuVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PopupMenuVC.swift 3 | // Flower 4 | // 5 | // Created by kingsic on 2022/2/3. 6 | // 7 | 8 | import UIKit 9 | 10 | class PopupMenuVC: UIViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | view.backgroundColor = .green 16 | 17 | let btn = UIButton(type: .contactAdd) 18 | btn.frame = CGRect(x: 10, y: 100, width: 50, height: 50) 19 | btn.addTarget(self, action: #selector(btn_action), for: .touchUpInside) 20 | btn.tag = 0 21 | view.addSubview(btn) 22 | 23 | let centerBtn = UIButton(type: .contactAdd) 24 | centerBtn.frame = CGRect(x: 0.5 * (screenWidth - 50), y: 100, width: 50, height: 50) 25 | centerBtn.addTarget(self, action: #selector(btn_action), for: .touchUpInside) 26 | centerBtn.tag = 1 27 | view.addSubview(centerBtn) 28 | 29 | let rightBtn = UIButton(type: .contactAdd) 30 | rightBtn.frame = CGRect(x: screenWidth - 60, y: 100, width: 50, height: 50) 31 | rightBtn.addTarget(self, action: #selector(btn_action), for: .touchUpInside) 32 | rightBtn.tag = 2 33 | view.addSubview(rightBtn) 34 | 35 | let bottomBtn = UIButton(type: .contactAdd) 36 | bottomBtn.frame = CGRect(x: 10, y: 300, width: 50, height: 50) 37 | bottomBtn.addTarget(self, action: #selector(btn_action), for: .touchUpInside) 38 | bottomBtn.tag = 3 39 | view.addSubview(bottomBtn) 40 | 41 | let bottomCenterBtn = UIButton(type: .contactAdd) 42 | bottomCenterBtn.frame = CGRect(x: 0.5 * (screenWidth - 50), y: 300, width: 50, height: 50) 43 | bottomCenterBtn.addTarget(self, action: #selector(btn_action), for: .touchUpInside) 44 | bottomCenterBtn.tag = 4 45 | view.addSubview(bottomCenterBtn) 46 | 47 | let bottomRightBtn = UIButton(type: .contactAdd) 48 | bottomRightBtn.frame = CGRect(x: screenWidth - 60, y: 300, width: 50, height: 50) 49 | bottomRightBtn.addTarget(self, action: #selector(btn_action), for: .touchUpInside) 50 | bottomRightBtn.tag = 5 51 | view.addSubview(bottomRightBtn) 52 | } 53 | 54 | @objc func btn_action(btn: UIButton) { 55 | if btn.tag == 1 { 56 | let config = SGPopupMenuConfigure() 57 | config.point = CGPoint(x: 0.5 * (screenWidth - config.width), y: btn.frame.maxY) 58 | config.backgroundColor = .black.withAlphaComponent(0.2) 59 | config.color = .black.withAlphaComponent(0.5) 60 | config.separatorColor = .black.withAlphaComponent(0.6) 61 | config.separatorInset = .init(left: 15, right: 15) 62 | config.textColor = .white 63 | config.textAlignment = .center 64 | config.height = 50 65 | 66 | let menu = SGPopupMenu(configure: config) 67 | menu.dataSource = ["发起群聊", "添加朋友", "扫一扫", "收付款"] 68 | menu.clickBlock { popupMenu, index in 69 | print("index - - \(index)") 70 | } 71 | popupMenu(menu) 72 | } else if btn.tag == 2 { 73 | let config = SGPopupMenuConfigure() 74 | config.point = CGPoint(x: btn.frame.maxX - config.width - 3, y: btn.frame.maxY) 75 | config.backgroundColor = .black.withAlphaComponent(0.2) 76 | config.separatorColor = .black.withAlphaComponent(0.6) 77 | config.separatorInset = .init(left: 15, right: 15) 78 | config.triangleLocation = .topRight 79 | config.width = 135 80 | config.imageViewBlock { imageView, index in 81 | imageView.image = UIImage(named: "image") 82 | } 83 | 84 | let menu = SGPopupMenu(configure: config) 85 | menu.dataSource = ["发起群聊", "添加朋友", "扫一扫", "收付款"] 86 | menu.clickBlock { popupMenu, index in 87 | print("index - - \(index)") 88 | } 89 | popupMenu(menu) 90 | } else if btn.tag == 3 { 91 | let config = SGPopupMenuConfigure() 92 | config.point = CGPoint(x: btn.frame.minX + 3, y: btn.frame.maxY - 4 * 44 - 60) 93 | config.triangleLocation = .topLeft 94 | config.width = 135 95 | config.triangleLocation = .bottomLeft 96 | config.imageViewBlock { imageView, index in 97 | imageView.image = UIImage(named: "image") 98 | } 99 | 100 | let vc = SGPopupMenu(configure: config) 101 | vc.dataSource = ["发起群聊", "添加朋友", "扫一扫", "收付款"] 102 | popupMenu(vc) 103 | } else if btn.tag == 4 { 104 | let config = SGPopupMenuConfigure() 105 | config.point = CGPoint(x: 0.5 * (screenWidth - config.width), y: btn.frame.maxY - 4 * 50 - 60) 106 | config.backgroundColor = .black.withAlphaComponent(0.2) 107 | config.color = .black.withAlphaComponent(0.5) 108 | config.separatorColor = .black.withAlphaComponent(0.6) 109 | config.separatorInset = .init(left: 15, right: 15) 110 | config.textColor = .white 111 | config.height = 50 112 | config.triangleLocation = .bottomCenter 113 | 114 | let menu = SGPopupMenu(configure: config) 115 | menu.dataSource = ["发起群聊", "添加朋友", "扫一扫", "收付款"] 116 | menu.clickBlock { popupMenu, index in 117 | print("index - - \(index)") 118 | } 119 | popupMenu(menu) 120 | } else if btn.tag == 5 { 121 | let config = SGPopupMenuConfigure() 122 | config.point = CGPoint(x: btn.frame.maxX - config.width - 3, y: btn.frame.maxY - 4 * 44 - 50) 123 | config.backgroundColor = .black.withAlphaComponent(0.2) 124 | config.separatorColor = .black.withAlphaComponent(0.6) 125 | config.separatorInset = .init(left: 15, right: 15) 126 | config.triangleLocation = .bottomRight 127 | config.isTriangle = false 128 | 129 | let menu = SGPopupMenu(configure: config) 130 | menu.dataSource = ["发起群聊", "添加朋友", "扫一扫", "收付款"] 131 | menu.clickBlock { popupMenu, index in 132 | print("index - - \(index)") 133 | } 134 | popupMenu(menu) 135 | } else { 136 | let config = SGPopupMenuConfigure() 137 | config.point = CGPoint(x: btn.frame.minX + 3, y: btn.frame.maxY) 138 | config.triangleLocation = .topLeft 139 | config.width = 135 140 | config.imageViewBlock { imageView, index in 141 | imageView.image = UIImage(named: "image") 142 | } 143 | 144 | let vc = SGPopupMenu(configure: config) 145 | vc.dataSource = ["发起群聊", "添加朋友", "扫一扫", "收付款"] 146 | popupMenu(vc) 147 | } 148 | 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /Flower/Flower/Custom/SGBaseCollectionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SGBaseCollectionView.swift 3 | // SGBaseCollectionView 4 | // 5 | // Created by kingsic on 2020/10/23. 6 | // Copyright © 2020 kingsic. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @objc public protocol SGBaseCollectionViewDataSource: NSObjectProtocol { 12 | /// Number of returned items 13 | func collectionView(_ baseCollectionView: SGBaseCollectionView, numberOfItems: Int) -> Int 14 | /// Data source 15 | func collectionView(_ baseCollectionView: SGBaseCollectionView, cell: UICollectionViewCell, cellForItemAt index: Int) 16 | } 17 | 18 | @objc public protocol SGBaseCollectionViewDelegate: NSObjectProtocol { 19 | /// Click event of item 20 | func collectionView(_ baseCollectionView: SGBaseCollectionView, didSelectItemAt index: Int) 21 | } 22 | 23 | public class SGBaseCollectionView: UIView { 24 | /// DataSource 25 | public var dataSource: SGBaseCollectionViewDataSource? 26 | 27 | /// Delegate 28 | public var delegate: SGBaseCollectionViewDelegate? 29 | 30 | /// Reload data 31 | public func reloadData() { 32 | collectionView.reloadData() 33 | } 34 | 35 | /// Item size. consider the top and bottom values of the contentinset property when setting the height 36 | public var itemSize: CGSize? 37 | 38 | /// Item minimumLineSpacing 39 | public var minimumLineSpacing: CGFloat? { 40 | willSet { 41 | if let tempNewValue = newValue { 42 | CVFLayout.minimumLineSpacing = tempNewValue < 0 ? 0 : tempNewValue 43 | } 44 | } 45 | } 46 | 47 | /// Item minimumInteritemSpacing 48 | public var minimumInteritemSpacing: CGFloat? { 49 | willSet { 50 | if let tempNewValue = newValue { 51 | CVFLayout.minimumInteritemSpacing = tempNewValue < 0 ? 0 : tempNewValue 52 | } 53 | } 54 | } 55 | 56 | /// Default UIEdgeInsetsZero. add additional scroll area around content 57 | public var contentInset: UIEdgeInsets? { 58 | willSet { 59 | if let tempNewValue = newValue { 60 | collectionView.contentInset = UIEdgeInsets.init(top: tempNewValue.top, left: tempNewValue.left, bottom: tempNewValue.bottom, right: tempNewValue.right) 61 | } 62 | } 63 | } 64 | 65 | fileprivate var tempIdentifier: String = "tempIdentifier" 66 | // For each reuse identifier that the collection view will use, register either a class or a nib from which to instantiate a cell. 67 | // If a nib is registered, it must contain exactly 1 top level object which is a UICollectionViewCell. 68 | // If a class is registered, it will be instantiated via alloc/initWithFrame: 69 | public func register(_ cellClass: UICollectionViewCell.Type, reuseIdentifier identifier: String) { 70 | collectionView.register(cellClass, forCellWithReuseIdentifier: identifier.isEmpty ? "cellID" : identifier) 71 | tempIdentifier = identifier.isEmpty ? "cellID" : identifier 72 | } 73 | 74 | public func register(_ nibName: String, reuseIdentifier identifier: String) { 75 | let nib = UINib(nibName: nibName, bundle: nil) 76 | collectionView.register(nib, forCellWithReuseIdentifier: identifier.isEmpty ? "cellID" : identifier) 77 | tempIdentifier = identifier.isEmpty ? "cellID" : identifier 78 | } 79 | 80 | /// Default false. if true, stop on multiples of view bounds 81 | public var pagingEnabled: Bool { 82 | set { 83 | if newValue { 84 | collectionView.isPagingEnabled = newValue 85 | } 86 | } 87 | get { 88 | return false 89 | } 90 | } 91 | 92 | /// Default false. if true, scroll direction Horizontal 93 | public var scrollDirectionHorizontal: Bool? { 94 | willSet { 95 | if newValue == true { 96 | CVFLayout.scrollDirection = .horizontal 97 | } 98 | } 99 | } 100 | 101 | /// Default false. if true, show indicator while we are tracking. fades out after tracking 102 | public var showsHorizontalScrollIndicator: Bool? { 103 | willSet { 104 | if (newValue != nil) { 105 | collectionView.showsHorizontalScrollIndicator = newValue! 106 | } 107 | } 108 | } 109 | /// Default false. if true, show indicator while we are tracking. fades out after tracking 110 | public var showsVerticalScrollIndicator: Bool? { 111 | willSet { 112 | if (newValue != nil) { 113 | collectionView.showsVerticalScrollIndicator = newValue! 114 | } 115 | } 116 | } 117 | 118 | 119 | override init(frame: CGRect) { 120 | super.init(frame: frame) 121 | addSubview(collectionView) 122 | } 123 | required init?(coder: NSCoder) { 124 | fatalError("init(coder:) has not been implemented") 125 | } 126 | 127 | public override func layoutSubviews() { 128 | super.layoutSubviews() 129 | if let tempItemSize = itemSize { 130 | CVFLayout.itemSize = CGSize(width: tempItemSize.width, height: tempItemSize.height) 131 | } 132 | 133 | let cv_x: CGFloat = 0.0 134 | let cv_y: CGFloat = 0.0 135 | let cv_w = self.frame.size.width 136 | let cv_h = self.frame.size.height 137 | collectionView.frame = CGRect.init(x: cv_x, y: cv_y, width: cv_w, height: cv_h) 138 | } 139 | 140 | private lazy var CVFLayout: UICollectionViewFlowLayout = { 141 | let tempCVFLayout = UICollectionViewFlowLayout() 142 | return tempCVFLayout 143 | }() 144 | private lazy var collectionView: UICollectionView = { 145 | let tempCollectionView = UICollectionView(frame: .zero, collectionViewLayout: CVFLayout) 146 | tempCollectionView.delegate = self 147 | tempCollectionView.dataSource = self 148 | tempCollectionView.backgroundColor = self.backgroundColor 149 | tempCollectionView.showsHorizontalScrollIndicator = false 150 | tempCollectionView.showsVerticalScrollIndicator = false 151 | return tempCollectionView 152 | }() 153 | 154 | } 155 | 156 | extension SGBaseCollectionView: UICollectionViewDelegate, UICollectionViewDataSource { 157 | public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 158 | return dataSource?.collectionView(self, numberOfItems: section) ?? 0 159 | } 160 | 161 | public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 162 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: tempIdentifier, for: indexPath) 163 | if dataSource != nil && (dataSource?.responds(to: #selector(dataSource?.collectionView(_:cell:cellForItemAt:)))) != nil { 164 | dataSource?.collectionView(self, cell: cell, cellForItemAt: indexPath.item) 165 | } 166 | return cell 167 | } 168 | 169 | public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 170 | if delegate != nil && ((delegate?.responds(to: #selector(delegate?.collectionView(_:didSelectItemAt:)))) != nil) { 171 | delegate?.collectionView(self, didSelectItemAt: indexPath.row) 172 | } 173 | } 174 | } 175 | 176 | -------------------------------------------------------------------------------- /Flower/Sources/SGCycleScrollView/SGCycleScrollView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SGCycleScrollView.swift 3 | // Flower 4 | // 5 | // Created by kingsic on 2022/7/30. 6 | // 7 | // Warning:SGCycleScrollView 的高度必须为整数,否则会导致滚动错乱 8 | // 9 | 10 | import UIKit 11 | 12 | @objc public protocol SGCycleScrollViewDelegate: NSObjectProtocol { 13 | /// Item 的点击事件 14 | @objc optional func cycleScrollView(_ cycleScrollView: SGCycleScrollView, didSelectItemAt index: Int) 15 | 16 | /// Item 的滚动事件 17 | @objc optional func cycleScrollView(_ cycleScrollView: SGCycleScrollView, didEndScrollingToItemAt index: Int) 18 | } 19 | 20 | @objc public protocol SGCycleScrollViewDataSource: NSObjectProtocol { 21 | /// Item 的个数 22 | @objc optional func numberOfItems(_ cycleScrollView: SGCycleScrollView) -> Int 23 | 24 | /// Cell 25 | @objc optional func cycleScrollView(_ cycleScrollView: SGCycleScrollView, cell: UICollectionViewCell, cellForItemAt index: Int) 26 | } 27 | 28 | @objc public class SGCycleScrollView: UIView { 29 | 30 | /// Delegate 31 | @objc public weak var delegate: SGCycleScrollViewDelegate? 32 | 33 | /// DataSource 34 | @objc public weak var dataSource: SGCycleScrollViewDataSource? 35 | 36 | /// 是否无限循环,默认为:true 37 | @objc public var infiniteLoop: Bool = true 38 | 39 | /// 滚动所需时间,默认为:0.0 40 | @objc public var timeInterval: TimeInterval = 0.0 { 41 | didSet { 42 | if timeInterval > 0 { 43 | DispatchQueue.main.async { [self] in 44 | addTimer() 45 | } 46 | } 47 | } 48 | } 49 | 50 | /// 是否自动滚动,默认为:true 51 | @objc public var autoScroll: Bool = true { 52 | didSet { 53 | if autoScroll == false { 54 | removeTimer() 55 | } 56 | } 57 | } 58 | 59 | /// 是否支持手势拖拽,默认为:true 60 | @objc public var isScrollEnabled: Bool = true { 61 | didSet { 62 | if isScrollEnabled == false { 63 | collectionView.isScrollEnabled = false 64 | } 65 | } 66 | } 67 | 68 | 69 | /// 是否需要反弹效果,默认为:true 70 | @objc public var bounces: Bool = true { 71 | didSet { 72 | if bounces == false { 73 | collectionView.bounces = false 74 | } 75 | } 76 | } 77 | 78 | /// 滚动方向 79 | @objc public var scrollDirection: UICollectionView.ScrollDirection = .horizontal { 80 | willSet { 81 | flowLayout.scrollDirection = newValue 82 | } 83 | } 84 | 85 | 86 | fileprivate var tempIdentifier: String = "tempIdentifier" 87 | 88 | /// 纯代码注册 Cell 89 | @objc public func register(_ cellClass: UICollectionViewCell.Type, reuseIdentifier identifier: String) { 90 | collectionView.register(cellClass, forCellWithReuseIdentifier: identifier.isEmpty ? "cellID" : identifier) 91 | tempIdentifier = identifier.isEmpty ? "cellID" : identifier 92 | } 93 | 94 | /// 使用 Nib 注册 Cell 95 | @objc public func registerNib(_ name: String, reuseIdentifier identifier: String) { 96 | let nib = UINib(nibName: name, bundle: nil) 97 | collectionView.register(nib, forCellWithReuseIdentifier: identifier.isEmpty ? "cellID" : identifier) 98 | tempIdentifier = identifier.isEmpty ? "cellID" : identifier 99 | } 100 | 101 | /// 刷新数据 102 | @objc public func reloadData() { 103 | collectionView.reloadData() 104 | } 105 | 106 | 107 | public override init(frame: CGRect) { 108 | super.init(frame: frame) 109 | 110 | addSubview(collectionView) 111 | } 112 | 113 | required init?(coder: NSCoder) { 114 | super.init(coder: coder) 115 | 116 | addSubview(collectionView) 117 | } 118 | 119 | var numberOfItems: Int = 0 120 | 121 | var timer: Timer? 122 | 123 | lazy var flowLayout: UICollectionViewFlowLayout = { 124 | let flowLayout = UICollectionViewFlowLayout() 125 | flowLayout.minimumLineSpacing = 0 126 | flowLayout.minimumInteritemSpacing = 0 127 | flowLayout.scrollDirection = scrollDirection 128 | return flowLayout 129 | }() 130 | 131 | lazy var collectionView: UICollectionView = { 132 | let cv = UICollectionView(frame: bounds, collectionViewLayout: flowLayout) 133 | cv.delegate = self 134 | cv.dataSource = self 135 | cv.isPagingEnabled = true 136 | cv.showsVerticalScrollIndicator = false 137 | cv.showsHorizontalScrollIndicator = false 138 | return cv 139 | }() 140 | 141 | 142 | public override func layoutSubviews() { 143 | super.layoutSubviews() 144 | 145 | flowLayout.itemSize = bounds.size 146 | collectionView.frame = bounds 147 | 148 | collectionView.scrollToItem(at: IndexPath.init(item: 0, section: infiniteLoop == true ? 25 : 0), at: .bottom, animated: false) 149 | } 150 | } 151 | 152 | extension SGCycleScrollView { 153 | func addTimer() { 154 | 155 | guard autoScroll == true else { 156 | return 157 | } 158 | 159 | guard infiniteLoop == true else { 160 | return 161 | } 162 | 163 | removeTimer() 164 | 165 | timer = Timer(timeInterval: timeInterval, target: self, selector: #selector(updateUI), userInfo: nil, repeats: true) 166 | RunLoop.main.add(timer!, forMode: .common) 167 | } 168 | 169 | func removeTimer() { 170 | timer?.invalidate() 171 | timer = nil 172 | } 173 | 174 | @objc func updateUI() { 175 | let currentIndexPath = collectionView.indexPathsForVisibleItems.last 176 | 177 | let indexPath = NSIndexPath(item: currentIndexPath!.item, section: infiniteLoop == true ? 25 : 0) 178 | 179 | collectionView.scrollToItem(at: indexPath as IndexPath, at: .bottom, animated: false) 180 | 181 | var nextItem = indexPath.item + 1 182 | var nextSection = indexPath.section 183 | 184 | if nextItem == numberOfItems { 185 | nextItem = 0 186 | nextSection+=1 187 | } 188 | 189 | let nextIndexPath = IndexPath.init(item: nextItem, section: nextSection) 190 | 191 | collectionView.scrollToItem(at: nextIndexPath, at: .bottom, animated: true) 192 | } 193 | } 194 | 195 | extension SGCycleScrollView: UICollectionViewDelegate { 196 | public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 197 | guard delegate != nil else { 198 | return 199 | } 200 | 201 | if (delegate!.responds(to: #selector(delegate!.cycleScrollView(_:didSelectItemAt:)))) { 202 | delegate!.cycleScrollView!(self, didSelectItemAt: indexPath.item) 203 | } 204 | } 205 | 206 | public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { 207 | if autoScroll && timeInterval > 0 { 208 | removeTimer() 209 | } 210 | } 211 | 212 | public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 213 | if autoScroll && timeInterval > 0 { 214 | addTimer() 215 | } 216 | 217 | scrollViewDidEndScrollingAnimation(collectionView) 218 | } 219 | 220 | public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { 221 | guard delegate != nil else { 222 | return 223 | } 224 | 225 | let p = self.convert(collectionView.center, to: collectionView) 226 | let indexPath = collectionView.indexPathForItem(at: p) 227 | 228 | if delegate!.responds(to: #selector(delegate!.cycleScrollView(_:didEndScrollingToItemAt:))) { 229 | delegate!.cycleScrollView!(self, didEndScrollingToItemAt: indexPath!.item) 230 | } 231 | } 232 | } 233 | 234 | 235 | extension SGCycleScrollView: UICollectionViewDataSource { 236 | public func numberOfSections(in collectionView: UICollectionView) -> Int { 237 | infiniteLoop == true ? 50 : 1 238 | } 239 | 240 | public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 241 | guard let tempDataSource = dataSource else { 242 | return 1 243 | } 244 | numberOfItems = (tempDataSource.numberOfItems?(self))! 245 | return numberOfItems 246 | } 247 | 248 | public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 249 | 250 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: tempIdentifier, for: indexPath) 251 | 252 | guard let tempDataSource = dataSource else { 253 | return cell 254 | } 255 | 256 | if tempDataSource.responds(to: #selector(dataSource?.cycleScrollView(_:cell:cellForItemAt:))) { 257 | dataSource?.cycleScrollView?(self, cell: cell, cellForItemAt: indexPath.row) 258 | } 259 | return cell 260 | } 261 | 262 | } 263 | -------------------------------------------------------------------------------- /Flower/Sources/SGPopupMenu/SGPopupMenu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SGPopupMenu.swift 3 | // Flower 4 | // 5 | // Created by kingsic on 2022/2/2. 6 | // 7 | 8 | import UIKit 9 | 10 | public enum TriangleLocation { 11 | case topLeft, topCenter, topRight, bottomLeft, bottomCenter, bottomRight 12 | } 13 | 14 | public struct SeparatorInset { 15 | public var left: CGFloat = 0.0 16 | public var right: CGFloat = 0.0 17 | } 18 | 19 | public class SGPopupMenuConfigure: NSObject { 20 | /// 背景颜色,默认为:.clear 21 | public var backgroundColor: UIColor = .clear 22 | 23 | /// 位置 24 | public var point: CGPoint = .zero 25 | 26 | /// 颜色,默认为:.white 27 | public var color: UIColor = .white 28 | 29 | /// 圆角大小,默认为:10 30 | public var cornerRadius: CGFloat = 10 31 | 32 | /// 宽度,默认为:130 33 | public var width: CGFloat = 130 34 | 35 | /// 行高度,默认为:44 36 | public var height: CGFloat = 44 37 | 38 | /// 是否显示垂直方向的指示器,默认为:false(当数据源个数大于 7 时,设置为 true 时才起作用) 39 | public var showsVerticalScrollIndicator = false 40 | 41 | /// 分割线颜色,默认为:.black.withAlphaComponent(0.2) 42 | public var separatorColor: UIColor = .black.withAlphaComponent(0.2) 43 | 44 | /// 分割线左右边距 45 | public var separatorInset: SeparatorInset = .init() 46 | 47 | /// 点击 item 时的背景颜色,默认为:.black.withAlphaComponent(0.2) 48 | public var selectedBackgroundColor: UIColor = .black.withAlphaComponent(0.2) 49 | 50 | /// 文字颜色,默认为:.black 51 | public var textColor: UIColor = .black 52 | 53 | /// 文字大小,默认为:.systemFont(ofSize: 15) 54 | public var textFont: UIFont = .systemFont(ofSize: 15) 55 | 56 | /// 文字对齐样式,默认为:.left 57 | public var textAlignment: NSTextAlignment = .left 58 | 59 | public typealias ImageViewBlock = (_ imageView: UIImageView, _ index: Int) -> Void 60 | fileprivate var tempImageViewBlock: ImageViewBlock? 61 | /// 图片回调方法 62 | public func imageViewBlock(block: @escaping ImageViewBlock) { 63 | tempImageViewBlock = block 64 | } 65 | 66 | /// 是否需要三角形,默认为:true 67 | public var isTriangle: Bool = true 68 | 69 | /// 三角形位置,默认为:.topCenter 70 | public var triangleLocation: TriangleLocation = .topCenter 71 | 72 | /// 三角形位置的偏移量,默认为:15(当 triangleLocation = .Center 时,不起作用) 73 | public var triangleLocationOffset: CGFloat = 15 74 | 75 | /// 三角形的宽,默认为:12 76 | public var triangleWidth: CGFloat = 12 77 | 78 | /// 三角形的高,默认为:6 79 | public var triangleHeight: CGFloat = 6 80 | } 81 | 82 | 83 | public class SGPopupMenu: UIViewController { 84 | /// 配置类 85 | public var configure: SGPopupMenuConfigure! 86 | 87 | /// 数据源 88 | public var dataSource: Array? { 89 | didSet { 90 | dataSourceDidSet() 91 | } 92 | } 93 | 94 | public typealias SGPopupMenuBlock = (_ popupMenu: SGPopupMenu, _ index: Int) -> () 95 | private var block: SGPopupMenuBlock? 96 | /// 菜单 item 的点击回调方法 97 | public func clickBlock(block: @escaping SGPopupMenuBlock) { 98 | self.block = block 99 | } 100 | 101 | public init(configure: SGPopupMenuConfigure) { 102 | super.init(nibName: nil, bundle: nil) 103 | 104 | self.modalPresentationStyle = .custom 105 | self.configure = configure 106 | } 107 | 108 | required init?(coder: NSCoder) { 109 | fatalError("init(coder:) has not been implemented") 110 | } 111 | 112 | public override func viewDidLoad() { 113 | super.viewDidLoad() 114 | 115 | view.addSubview(popupMenu) 116 | popupMenu.addSubview(tableView) 117 | if configure.isTriangle { 118 | view.addSubview(triangleView) 119 | } 120 | } 121 | 122 | public override func viewDidAppear(_ animated: Bool) { 123 | super.viewDidAppear(animated) 124 | view.backgroundColor = configure.backgroundColor 125 | } 126 | 127 | fileprivate lazy var triangleView : TriangleView = { 128 | let view = TriangleView(frame: .zero, configure: configure) 129 | let width: CGFloat = configure.triangleWidth 130 | let height: CGFloat = configure.triangleHeight 131 | var y = popupMenu.frame.minY - height 132 | if configure.triangleLocation == .bottomLeft || configure.triangleLocation == .bottomCenter || configure.triangleLocation == .bottomRight { 133 | y = popupMenu.frame.maxY 134 | } 135 | var x: CGFloat = 0 136 | if configure.triangleLocation == .topRight { 137 | let offset = configure.triangleLocationOffset < 0 ? 0 : configure.triangleLocationOffset 138 | x = popupMenu.frame.maxX - width - offset 139 | } else if configure.triangleLocation == .topCenter { 140 | x = popupMenu.frame.midX - 0.5 * width 141 | } else if configure.triangleLocation == .topLeft { 142 | let offset = configure.triangleLocationOffset < 0 ? 0 : configure.triangleLocationOffset 143 | x = popupMenu.frame.minX + offset 144 | } else if configure.triangleLocation == .bottomLeft { 145 | let offset = configure.triangleLocationOffset < 0 ? 0 : configure.triangleLocationOffset 146 | x = popupMenu.frame.minX + offset 147 | } else if configure.triangleLocation == .bottomCenter { 148 | x = popupMenu.frame.midX - 0.5 * width 149 | } else if configure.triangleLocation == .bottomRight { 150 | let offset = configure.triangleLocationOffset < 0 ? 0 : configure.triangleLocationOffset 151 | x = popupMenu.frame.maxX - width - offset 152 | } 153 | view.frame = CGRect(x: x, y: y, width: width, height: height) 154 | view.backgroundColor = .clear 155 | return view 156 | }() 157 | 158 | lazy var popupMenu: UIView = { 159 | let menu = UIView() 160 | menu.backgroundColor = configure.color 161 | let width = configure.width 162 | menu.frame = CGRect(x: configure.point.x, y: configure.point.y, width: width, height: 0) 163 | menu.layer.cornerRadius = configure.cornerRadius 164 | return menu 165 | }() 166 | 167 | lazy var tableView: UITableView = { 168 | let tableView = UITableView(frame: popupMenu.bounds, style: .plain) 169 | tableView.delegate = self 170 | tableView.dataSource = self 171 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cellID") 172 | tableView.backgroundColor = .clear 173 | tableView.tableFooterView = UIView() 174 | tableView.showsHorizontalScrollIndicator = false 175 | tableView.rowHeight = configure.height 176 | tableView.separatorColor = configure.separatorColor 177 | tableView.showsVerticalScrollIndicator = configure.showsVerticalScrollIndicator 178 | tableView.layer.cornerRadius = configure.cornerRadius 179 | return tableView 180 | }() 181 | } 182 | 183 | public extension UIViewController { 184 | func popupMenu(_ menu: SGPopupMenu) { 185 | present(menu, animated: false, completion: nil) 186 | } 187 | } 188 | 189 | extension SGPopupMenu { 190 | func dataSourceDidSet() { 191 | guard dataSource?.count != 0 else { 192 | return 193 | } 194 | 195 | if dataSource!.count >= 7 { 196 | popupMenu.frame.size.height = 7 * configure.height 197 | tableView.frame.size.height = popupMenu.frame.size.height 198 | } else { 199 | tableView.bounces = false 200 | popupMenu.frame.size.height = CGFloat(dataSource!.count) * configure.height 201 | tableView.frame.size.height = popupMenu.frame.size.height 202 | } 203 | tableView.reloadData() 204 | } 205 | } 206 | 207 | extension SGPopupMenu: UITableViewDelegate, UITableViewDataSource { 208 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 209 | return dataSource?.count ?? 0 210 | } 211 | 212 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 213 | let cell = tableView.dequeueReusableCell(withIdentifier: "cellID", for: indexPath) 214 | let selectedBackgroundView = UIView() 215 | selectedBackgroundView.backgroundColor = configure.selectedBackgroundColor 216 | cell.selectedBackgroundView = selectedBackgroundView 217 | cell.backgroundColor = .clear 218 | cell.textLabel?.text = dataSource![indexPath.row] 219 | cell.textLabel?.textColor = configure.textColor 220 | cell.textLabel?.font = configure.textFont 221 | cell.textLabel?.textAlignment = configure.textAlignment 222 | if let block = configure.tempImageViewBlock { 223 | block(cell.imageView!, indexPath.row) 224 | } 225 | 226 | if indexPath.row == dataSource!.count - 1 { 227 | cell.separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: cell.bounds.width) 228 | } else { 229 | if configure.separatorInset.left != 0 && configure.separatorInset.right != 0 { 230 | let left = configure.separatorInset.left < 0 ? 0 : configure.separatorInset.left 231 | let right = configure.separatorInset.right < 0 ? 0 : configure.separatorInset.right 232 | cell.separatorInset = UIEdgeInsets(top: 0, left: left, bottom: 0, right: right) 233 | } 234 | } 235 | return cell 236 | } 237 | 238 | public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 239 | dismiss(animated: false) { [self] in 240 | if let tempBlock = block { 241 | tempBlock(self, indexPath.row) 242 | } 243 | } 244 | } 245 | } 246 | 247 | extension SGPopupMenu { 248 | public override func touchesBegan(_ touches: Set, with event: UIEvent?) { 249 | dismiss(animated: false, completion: nil) 250 | } 251 | } 252 | 253 | 254 | fileprivate class TriangleView: UIView { 255 | var configure: SGPopupMenuConfigure! 256 | 257 | init(frame: CGRect, configure: SGPopupMenuConfigure) { 258 | super.init(frame: frame) 259 | 260 | self.configure = configure 261 | } 262 | 263 | required init?(coder: NSCoder) { 264 | fatalError("init(coder:) has not been implemented") 265 | } 266 | 267 | override func draw(_ rect: CGRect) { 268 | super.draw(rect) 269 | 270 | let context = UIGraphicsGetCurrentContext() 271 | if configure.triangleLocation == .bottomLeft || configure.triangleLocation == .bottomCenter || configure.triangleLocation == .bottomRight { 272 | context?.move(to: CGPoint(x: rect.size.width * 0.5, y: rect.size.height)) 273 | context?.addLine(to: CGPoint(x: 0, y: 0)) 274 | context?.addLine(to: CGPoint(x: rect.size.width, y: 0)) 275 | } else { 276 | context?.move(to: CGPoint(x: rect.size.width * 0.5, y: 0)) 277 | context?.addLine(to: CGPoint(x: 0, y: rect.size.height)) 278 | context?.addLine(to: CGPoint(x: rect.size.width, y: rect.size.height)) 279 | } 280 | 281 | context?.closePath() 282 | configure.color.setStroke() 283 | configure.color.setFill() 284 | context?.drawPath(using: .fillStroke) 285 | } 286 | } 287 | 288 | --------------------------------------------------------------------------------