├── 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 |  
56 |
57 |  
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 |
--------------------------------------------------------------------------------