├── .gitignore ├── README.md ├── c ├── CrifanLib.h ├── CrifanLibDemo.c ├── CrifanLibDemo.h ├── JailbreakPathList.c ├── JailbreakPathList.h └── crifanLib.c ├── csharp ├── crifanLib.cs ├── crifanLibAmazon.cs ├── crifanLibAws.cs └── crifanLibGoogle.cs ├── go └── crifanLib.go ├── iOS ├── CrifanLibiOS.h ├── CrifanLibiOS.m ├── HookLogiOS.h ├── HookLogiOS.m ├── JailbreakiOS.h ├── JailbreakiOS.m ├── OpenFileiOS.h └── OpenFileiOS.m ├── java ├── ByteUtil.java ├── CompressionUtil.java ├── JsonMapUtil.java ├── ListUtil.java ├── UrlUtil.java ├── crifanLib.java ├── crifanLibOkHttp.java └── crifanLibSongtaste.java ├── javascript ├── common.js ├── datetime.js ├── dict.js ├── frida │ └── README.md ├── list.js ├── storage.js ├── string.js ├── type.js └── url.js ├── php └── crifanLib.php ├── python ├── README.md └── crifanLib │ └── README.md └── swift ├── CrifanCGSize.swift ├── CrifanCollection.swift ├── CrifanInt.swift ├── CrifanLib ├── CrifanLib.swift └── CrifanLibDemo.swift ├── CrifanNSDate.swift ├── Device └── CrifanDevice.swift ├── Http ├── CrifanLibHttp.swift └── CrifanLibHttpDemo.swift ├── String ├── CrifanString.swift └── CrifanStringDemo.swift ├── ThirdParty ├── Charts │ └── CrifanCharts.swift └── GridView │ ├── CustomCollectionViewLayout.swift │ ├── GridCell.swift │ ├── GridView.swift │ └── GridViewDemo.swift ├── Thread ├── CrifanThread.swift └── CrifanThreadDemo.swift └── UI ├── Button ├── BadgeButton │ ├── BadgeButton.swift │ └── BadgeButtonDemo.swift ├── CommonButton.swift ├── CountdownButton │ ├── CountdownButton.swift │ └── CountdownButtonDemo.swift └── ImageLabelBadgeButton.swift ├── CrifanUILabelUITextView.swift ├── Image ├── CrifanUIImage.swift └── CrifanUIImageDemo.swift ├── TableView ├── CrifanUITableViewCell.swift ├── LeftLabelRightRadioButtonTableViewCell.swift ├── LeftLabelRightSegmentedControlTableViewCell.swift ├── LeftLabelRightTextfieldTableViewCell.swift └── TextviewTableViewCell.swift └── ViewController ├── AutoMoveUpViewController.swift ├── AutoMoveUpViewController └── AutoMoveUpViewController.swift ├── CrifanViewController.swift ├── ImagePickerViewController └── ImagePickerViewController.swift ├── ImageViewerController └── ImageViewerController.swift ├── LeftLabelRightSelectableTableViewCell.swift ├── LeftLabelRightTextviewTableViewCell.swift ├── LeftLabelTableViewCell.swift ├── SelectItemViewController └── SelectItemViewController.swift ├── TapToHideViewController.swift └── TapToHideViewController └── TapToHideViewController.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | .vscode/ 4 | 5 | __pycache__/ 6 | *.pyc 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # crifanLib 2 | 3 | * update: `20241211` 4 | 5 | Crifan's library for various programming language 6 | 7 | ## repo 8 | 9 | * https://github.com/crifan/crifanLib 10 | * old: https://code.google.com/archive/p/crifanlib/ 11 | 12 | ## Sub repos 13 | 14 | * Frida 15 | * https://github.com/crifan/JsFridaUtil 16 | * Python 17 | * https://github.com/crifan/crifanLibPython 18 | 19 | ## Doc / Usage 20 | 21 | * new 22 | * for `c` and `iOS` 23 | * iOSJailbreakDetection 24 | * https://github.com/crifan/iOSJailbreakDetection/tree/main/iOSJailbreakDetection/libs 25 | * iOSBypassJailbreak 26 | * https://github.com/crifan/iOSBypassJailbreak/tree/main/iOSBypassJailbreak/libs 27 | * iOSYouTubeAdsFilter 28 | * https://github.com/crifan/iOSYouTubeAdsFilter/tree/main/youtubeDylib/libs 29 | * iOSOpenDevHookTemplate 30 | * https://github.com/crifan/iOSOpenDevHookTemplate/tree/main/iOSOpenDevHookTemplate/iOSOpenDevHookTemplate/libs 31 | * for `c` 32 | * related 33 | * https://book.crifan.org/books/c_lang_dev_summary/website/c_summary/ 34 | * https://book.crifan.org/books/ios_dev_summary/website/objc_dev/lang/ 35 | * old 36 | * http://www.crifan.org/crifan_released_all/crifanlib/ 37 | * [crifanLib.py](https://www.crifan.org/files/doc/docbook/crifanlib_python/release/html/crifanlib_python.html) 38 | * [crifanLib.cs](https://www.crifan.org/files/doc/docbook/crifanlib_csharp/release/html/crifanlib_csharp.html) 39 | -------------------------------------------------------------------------------- /c/CrifanLib.h: -------------------------------------------------------------------------------- 1 | /* 2 | File: CrifanLib.h 3 | Function: crifan's common C libs header file 4 | Author: Crifan Li 5 | Latest: https://github.com/crifan/crifanLib/blob/master/c/CrifanLib.h 6 | Updated: 20220602_1526 7 | */ 8 | 9 | // This will not work with all C++ compilers, but it works with clang and gcc 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | #ifndef CrifanLib_h 15 | #define CrifanLib_h 16 | 17 | //#import 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | //#include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include /* PATH_MAX */ 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | // integer 37 | bool isIntInList(int valueToCheck, int* intList, int intListLen); 38 | 39 | // Date Time 40 | //char* getCompileDateTimeStr(void); 41 | char* parseTimeInfo(char* dateTimeStr, struct tm* outTimeInfo); 42 | bool isTimeExpired(const char* expiredTimeStr); 43 | 44 | // char 45 | void initRandomChar(void); 46 | char randomChar(const char* choiceStr); 47 | 48 | // string 49 | char* randomStr(int strLen, const char* choiceStr); 50 | char* boolToStr(bool curBool); 51 | char* strToLowercase(const char* origStr); 52 | bool strStartsWith(const char *fullStr, const char *prefixStr); 53 | bool strEndsWith(const char* fullStr, const char* endStr); 54 | //char* removeHead(const char* fullStr, const char* headStr); 55 | char* removeHead(const char* fullStr, const char* headStr, char** toFreePtr); 56 | char* removeTail(const char* fullStr, const char* tailStr); 57 | char* removeEndSlash(const char* origPath); 58 | char* strReplace(const char *fullStr, const char *replaceFromStr, const char *replaceToStr); 59 | void strSplit(const char* fullStr, const char* delim, char*** resultSubStrListPtr, int* resultListLenPtr); 60 | void writeStrToFile(char* filePath, char* outputStr); 61 | 62 | // file size 63 | long calulateFilesize_fgetc(char* inputFilename); 64 | long calulateFilesize_ftell(char* inputFilename); 65 | long calulateFilesize_fstat(char* inputFilename); 66 | 67 | // file mode 68 | char* fileSizeToStr(off_t fileStSize); 69 | void fileModeToStr(mode_t mode, char * modeStrBuf); 70 | char* fileTypeToChar(mode_t mode); 71 | char* statToStr(struct stat* statInfo); 72 | 73 | // file path 74 | char* removeTwoDotPart(const char* origPath); 75 | bool isPathEaqual(const char* path1, const char* path2); 76 | char* toPurePath(const char* origPath); 77 | bool parseRealPath(const char* curPath, char* gotRealPath); 78 | 79 | #define strPathJoin(...) _strPathJoin(__VA_ARGS__, NULL); 80 | char* _strPathJoin(const char* firstPath, ...); 81 | 82 | bool getFilePath(int fd, char* outFilePath); 83 | 84 | // iOS 85 | int iOS_system(const char* command); 86 | void iOS_antiDebug_ptrace(void); 87 | void iOS_antiDebug_syscall(void); 88 | void iOS_antiDebug_svc0x80_syscall(void); 89 | 90 | #endif /* CrifanLib_h */ 91 | 92 | #ifdef __cplusplus 93 | } 94 | #endif 95 | -------------------------------------------------------------------------------- /c/CrifanLibDemo.h: -------------------------------------------------------------------------------- 1 | /* 2 | File: CrifanLibDemo.h 3 | Function: crifan's common C lib function demo header 4 | Author: Crifan Li 5 | Latest: https://github.com/crifan/crifanLib/blob/master/c/CrifanLibDemo.h 6 | Updated: 20220124_1552 7 | */ 8 | 9 | #ifndef CrifanLibDemo_h 10 | #define CrifanLibDemo_h 11 | 12 | void testCustomStrstr(void); 13 | void testConst(void); 14 | void testRandomStr(void); 15 | void testIsIntInList(void); 16 | void testParsePurePath(void); 17 | void testPathCompare(void); 18 | void testPathJoin(void); 19 | void testJbPathDetect(void); 20 | void testLowcase(void); 21 | void showCalculateElapsedTime(void); 22 | 23 | void testExpired_compileTime(void); 24 | void testExpired_defineTime(void); 25 | void testTimeDate(void); 26 | 27 | #endif /* CrifanLibDemo_h */ 28 | -------------------------------------------------------------------------------- /c/JailbreakPathList.h: -------------------------------------------------------------------------------- 1 | /* 2 | File: JailbreakPathList.h 3 | Function: crifan's common jailbreak file path list header file 4 | Author: Crifan Li 5 | Latest: https://github.com/crifan/crifanLib/blob/master/c/JailbreakPathList.h 6 | Updated: 20211230_1049 7 | */ 8 | 9 | // This will not work with all C++ compilers, but it works with clang and gcc 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | #ifndef JailbreakPathList_h 15 | #define JailbreakPathList_h 16 | 17 | #include 18 | 19 | #include "CrifanLib.h" 20 | 21 | extern const int jailbreakPathListLen; 22 | extern const char* jailbreakPathList_Dylib[]; 23 | extern const char* jailbreakPathList_Other[]; 24 | //extern char* jailbreakPathList_Dylib[]; 25 | //extern char* jailbreakPathList_Other[]; 26 | extern const int jailbreakPathListLen_Dylib; 27 | extern const int jailbreakPathListLen_Other; 28 | 29 | //extern const char* jailbreakPathList[]; 30 | const char** getJailbreakPathList(void); 31 | //char** getJailbreakPathList(void); 32 | 33 | bool isPathInJailbreakPathList(const char *curPath); 34 | bool isJailbreakPath_pureC(const char *curPath); 35 | bool isJailbreakPath_realpath(const char *pathname); 36 | bool isJailbreakPath(const char *pathname); 37 | bool isJailbreakDylib(const char *pathname); 38 | bool isJailbreakDylibFunctionName(const char *libFuncName); 39 | 40 | bool isPathInList( 41 | const char* inputPath, 42 | // char* inputPath, 43 | const char** pathList, 44 | // char** pathList, 45 | int pathListLen, 46 | bool isConvertToPurePath, // is convert to pure path or not 47 | bool isCmpSubFolder // is compare sub foder or not 48 | ); 49 | 50 | #endif /* JailbreakPathList_h */ 51 | 52 | #ifdef __cplusplus 53 | } 54 | #endif 55 | -------------------------------------------------------------------------------- /go/crifanLib.go: -------------------------------------------------------------------------------- 1 | /* 2 | * [File] 3 | * crifanLib..go 4 | * 5 | * [Function] 6 | * 【记录】在用go语言成功模拟登陆百度后把相关函数整理至自己的go语言的库函数:crifanLib.go 7 | * http://www.crifan.com/after_use_go_language_emulate_login_baidu_arrage_common_func_to_crifanlib_go 8 | * 9 | * [Version] 10 | * 2013-09-21 11 | * 12 | * [Contact] 13 | * http://www.crifan.com/about/me/ 14 | */ 15 | package crifanLib 16 | 17 | import ( 18 | //"fmt" 19 | //"log" 20 | "os" 21 | //"runtime" 22 | //"path" 23 | //"strings" 24 | "time" 25 | //"io" 26 | "io/ioutil" 27 | "net/http" 28 | "net/http/cookiejar" 29 | "net/url" 30 | //"sync" 31 | //"net/url" 32 | //"regexp" 33 | //"bufio" 34 | "bytes" 35 | ) 36 | 37 | //import l4g "log4go.googlecode.com/hg" 38 | //import l4g "code.google.com/p/log4go" 39 | import "code.google.com/p/log4go" 40 | 41 | /*************************************************************************************************** 42 | Global Variables 43 | ***************************************************************************************************/ 44 | var gCurCookies []*http.Cookie; 45 | var gCurCookieJar *cookiejar.Jar; 46 | var gLogger log4go.Logger; 47 | 48 | /*************************************************************************************************** 49 | Private Functions 50 | ***************************************************************************************************/ 51 | //init for crifanLib 52 | func init(){ 53 | gCurCookies = nil 54 | gCurCookieJar,_ = cookiejar.New(nil) 55 | gLogger = nil 56 | 57 | //InitLogger() // caller should manually call this 58 | } 59 | 60 | // //de-init for all 61 | // func deinitAll(){ 62 | // gCurCookies = nil 63 | // if(nil == gLogger) { 64 | // gLogger.Close(); 65 | // //os.Stdout.Sync() //try manually flush, but can not fix log4go's flush bug 66 | 67 | // gLogger = nil 68 | // } 69 | // } 70 | 71 | /*************************************************************************************************** 72 | Public Functions 73 | ***************************************************************************************************/ 74 | 75 | //init for logger 76 | func InitLogger(logFilename string) log4go.Logger { 77 | //var filenameOnly string = GetCurFilename() 78 | //var logFilename string = filenameOnly + ".log"; 79 | 80 | //gLogger = log4go.NewLogger() 81 | //gLogger = make(log4go.Logger) 82 | 83 | //for console 84 | //gLogger.AddFilter("stdout", log4go.INFO, log4go.NewConsoleLogWriter()) 85 | gLogger = log4go.NewDefaultLogger(log4go.INFO) 86 | 87 | //for log file 88 | if _, err := os.Stat(logFilename); err == nil { 89 | //fmt.Printf("found old log file %s, now remove it\n", logFilename) 90 | os.Remove(logFilename) 91 | } 92 | //gLogger.AddFilter("logfile", log4go.FINEST, log4go.NewFileLogWriter(logFilename, true)) 93 | //gLogger.AddFilter("logfile", log4go.FINEST, log4go.NewFileLogWriter(logFilename, false)) 94 | gLogger.AddFilter("log", log4go.FINEST, log4go.NewFileLogWriter(logFilename, false)) 95 | gLogger.Debug("Current time is : %s", time.Now().Format("15:04:05 MST 2006/01/02")) 96 | 97 | return gLogger 98 | } 99 | 100 | // //get current logger 101 | // func GetCurLogger() log4go.Logger { 102 | // return gLogger 103 | // } 104 | 105 | 106 | //get url response html 107 | func GetUrlRespHtml(strUrl string, postDict map[string]string) string{ 108 | gLogger.Debug("in getUrlRespHtml, strUrl=%s", strUrl) 109 | gLogger.Debug("postDict=%s", postDict) 110 | 111 | var respHtml string = ""; 112 | 113 | httpClient := &http.Client{ 114 | //Transport:nil, 115 | //CheckRedirect: nil, 116 | Jar:gCurCookieJar, 117 | } 118 | 119 | var httpReq *http.Request 120 | //var newReqErr error 121 | if nil == postDict { 122 | gLogger.Debug("is GET") 123 | //httpReq, newReqErr = http.NewRequest("GET", strUrl, nil) 124 | httpReq, _ = http.NewRequest("GET", strUrl, nil) 125 | // ... 126 | //httpReq.Header.Add("If-None-Match", `W/"wyzzy"`) 127 | } else { 128 | //【记录】go语言中实现http的POST且传递对应的post data 129 | //http://www.crifan.com/go_language_http_do_post_pass_post_data 130 | gLogger.Debug("is POST") 131 | postValues := url.Values{} 132 | for postKey, PostValue := range postDict{ 133 | postValues.Set(postKey, PostValue) 134 | } 135 | gLogger.Debug("postValues=%s", postValues) 136 | postDataStr := postValues.Encode() 137 | gLogger.Debug("postDataStr=%s", postDataStr) 138 | postDataBytes := []byte(postDataStr) 139 | gLogger.Debug("postDataBytes=%s", postDataBytes) 140 | postBytesReader := bytes.NewReader(postDataBytes) 141 | //httpReq, newReqErr = http.NewRequest("POST", strUrl, postBytesReader) 142 | httpReq, _ = http.NewRequest("POST", strUrl, postBytesReader) 143 | //httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") 144 | httpReq.Header.Add("Content-Type", "application/x-www-form-urlencoded") 145 | } 146 | 147 | httpResp, err := httpClient.Do(httpReq) 148 | // ... 149 | 150 | //httpResp, err := http.Get(strUrl) 151 | //gLogger.Info("http.Get done") 152 | if err != nil { 153 | gLogger.Warn("http get strUrl=%s response error=%s\n", strUrl, err.Error()) 154 | } 155 | gLogger.Debug("httpResp.Header=%s", httpResp.Header) 156 | gLogger.Debug("httpResp.Status=%s", httpResp.Status) 157 | 158 | defer httpResp.Body.Close() 159 | // gLogger.Info("defer httpResp.Body.Close done") 160 | 161 | body, errReadAll := ioutil.ReadAll(httpResp.Body) 162 | //gLogger.Info("ioutil.ReadAll done") 163 | if errReadAll != nil { 164 | gLogger.Warn("get response for strUrl=%s got error=%s\n", strUrl, errReadAll.Error()) 165 | } 166 | //gLogger.Debug("body=%s\n", body) 167 | 168 | //gCurCookies = httpResp.Cookies() 169 | //gCurCookieJar = httpClient.Jar; 170 | gCurCookies = gCurCookieJar.Cookies(httpReq.URL); 171 | //gLogger.Info("httpResp.Cookies done") 172 | 173 | //respHtml = "just for test log ok or not" 174 | respHtml = string(body) 175 | //gLogger.Info("httpResp body []byte to string done") 176 | 177 | return respHtml 178 | } 179 | 180 | //get current http cookies 181 | func GetCurCookies() []*http.Cookie { 182 | return gCurCookies 183 | } 184 | 185 | //print http cookies 186 | func DbgPrintCookies(httpCookies []*http.Cookie) { 187 | if nil != httpCookies { 188 | var cookieNum int = len(httpCookies); 189 | gLogger.Debug("cookieNum=%d", cookieNum) 190 | for i := 0; i < cookieNum; i++ { 191 | var curCk *http.Cookie = httpCookies[i]; 192 | gLogger.Debug("------ Cookie [%d]------", i) 193 | gLogger.Debug("Name\t\t=%s", curCk.Name) 194 | gLogger.Debug("Value\t=%s", curCk.Value) 195 | gLogger.Debug("Path\t\t=%s", curCk.Path) 196 | gLogger.Debug("Domain\t=%s", curCk.Domain) 197 | gLogger.Debug("Expires\t=%s", curCk.Expires) 198 | gLogger.Debug("RawExpires\t=%s", curCk.RawExpires) 199 | gLogger.Debug("MaxAge\t=%d", curCk.MaxAge) 200 | gLogger.Debug("Secure\t=%t", curCk.Secure) 201 | gLogger.Debug("HttpOnly\t=%t", curCk.HttpOnly) 202 | gLogger.Debug("Unparsed\t=%s", curCk.Unparsed) 203 | gLogger.Debug("Raw\t\t=%s", curCk.Raw) 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /iOS/CrifanLibiOS.h: -------------------------------------------------------------------------------- 1 | /* 2 | File: CrifanLibiOS.h 3 | Function: crifan's common iOS function 4 | Author: Crifan Li 5 | Latest: https://github.com/crifan/crifanLib/blob/master/iOS/CrifanLibiOS.h 6 | Updated: 20241123_1748 7 | */ 8 | 9 | #import 10 | #import 11 | #import 12 | #import 13 | #import 14 | 15 | #import "CrifanLib.h" 16 | 17 | /*============================================================================== 18 | iOS Related 19 | ==============================================================================*/ 20 | 21 | NS_ASSUME_NONNULL_BEGIN 22 | 23 | @interface CrifanLibiOS : NSObject 24 | 25 | /*============================================================================== 26 | String List 27 | ==============================================================================*/ 28 | 29 | //+ (NSArray *) strListToNSArray: (char*_Nullable*_Nullable)strList listCount:(int)listCount; 30 | + (NSArray *) strListToNSArray: (char*_Nonnull*_Nonnull)strList listCount:(int)listCount; 31 | //NSMutableArray* splitToLines(NSString* largeStr, int maxLenPerLine); 32 | +(NSMutableArray*) splitToLines: (NSString*)largeStr maxLenPerLine:(int)maxLenPerLine; 33 | 34 | /*============================================================================== 35 | NSArray 36 | ==============================================================================*/ 37 | 38 | + (NSString*) nsStrListToStr: (NSArray*)curList; 39 | + (NSString*) nsStrListToStr: (NSArray*)curList isSortList:(BOOL)isSortList isAddIndexPrefix:(BOOL)isAddIndexPrefix; 40 | 41 | /*============================================================================== 42 | Codesign 43 | ==============================================================================*/ 44 | 45 | + (BOOL) isCodeSignExist; 46 | + (NSString*) getEmbeddedCodesign; 47 | + (NSString*) getAppId; 48 | + (BOOL) isSelfAppId: (NSString*) selfAppId; 49 | 50 | /*============================================================================== 51 | Process 52 | ==============================================================================*/ 53 | 54 | + (NSArray *)runningProcesses; 55 | + (NSArray *)printCallStack; 56 | 57 | @end 58 | 59 | NS_ASSUME_NONNULL_END 60 | -------------------------------------------------------------------------------- /iOS/HookLogiOS.h: -------------------------------------------------------------------------------- 1 | /* 2 | File: HookLogiOS.h 3 | Function: crifan's common iOS hook log functions header 4 | Author: Crifan Li 5 | Latest: https://github.com/crifan/crifanLib/blob/master/iOS/HookLogiOS.h 6 | Updated: 20241214_1207 7 | */ 8 | 9 | // This will not work with all C++ compilers, but it works with clang and gcc 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | #ifndef HookLogiOS_h 15 | #define HookLogiOS_h 16 | 17 | 18 | #import 19 | #import 20 | 21 | /*============================================================================== 22 | Global Config 23 | ==============================================================================*/ 24 | 25 | // Note: os_log max length/limit is 1K=1024 26 | // when split large NSString, for single line string length will use this definition 27 | extern int LOG_MAX_LEN_ONCE; 28 | 29 | // output one log every N time 30 | extern long LOG_ONCE_PER_NUM; 31 | 32 | /*============================================================================== 33 | Global Variable 34 | ==============================================================================*/ 35 | 36 | extern long gCurLogNum; 37 | 38 | extern long gNoUse; 39 | 40 | /*============================================================================== 41 | Functions 42 | ==============================================================================*/ 43 | 44 | bool nonEmptyHeader(id _Nullable curHeaderDict); 45 | void logLargeStr(NSString* _Nonnull largeStr); 46 | void logPossibleLargeStr(NSString* _Nonnull possibleLargeStr); 47 | void printCallStack(NSString* _Nullable prefix); 48 | void printCallStack_largeStr(NSString* _Nullable prefix); 49 | 50 | void dbgWriteClsDescToFile(char* _Nonnull className, id _Nonnull classObj); 51 | 52 | /*============================================================================== 53 | Common Define 54 | ==============================================================================*/ 55 | 56 | // String 57 | #define STR_EMPTY "" 58 | #define IS_EMPTY_STR(curStr) (0 == strcmp(curStr, STR_EMPTY)) 59 | 60 | // Log 61 | 62 | #define ERROR_STR(curErr) ((error != NULL) ? *error: @"") 63 | 64 | #define HOOK_PREFIX(isEnable) (isEnable ? "":"no_hook ") 65 | 66 | //#ifdef FOR_RELEASE 67 | #ifdef DISABLE_ALL_IOS_LOG 68 | 69 | //// for debug 70 | //#define IOS_LOG_INFO_ENABLE 1 71 | #define IOS_LOG_INFO_ENABLE 0 72 | 73 | #define IOS_LOG_DEBUG_ENABLE 0 74 | #define IOS_LOG_ERROR_ENABLE 0 75 | 76 | #else 77 | 78 | #define IOS_LOG_INFO_ENABLE 1 79 | #define IOS_LOG_DEBUG_ENABLE 0 80 | #define IOS_LOG_ERROR_ENABLE 1 81 | 82 | #endif 83 | 84 | //// hook_openFile.xm -> hook_openFile 85 | //#define FILENAME_NO_SUFFIX (strrchr(__FILE_NAME__, '.') ? strrchr(__FILE_NAME__, '.') + 1 : __FILE_NAME__) 86 | 87 | // // _logos_function$_ungrouped$open -> open 88 | // #define PURE_FUNC (strrchr(__func__, '$') ? strrchr(__func__, '$') + 1 : __func__) 89 | 90 | // // _logos_method$_ungrouped$NSFileManager$fileExistsAtPath$ -> fileExistsAtPath$ 91 | 92 | 93 | #define UNGROUP_STR "_ungrouped$" 94 | #define UNGROUP_LEN strlen(UNGROUP_STR) 95 | #define HOOK_ "hook_" 96 | //#define HOOK_SPACE "hook_ " 97 | 98 | // Method 1: 99 | // // _logos_method$_ungrouped$NSFileManager$fileExistsAtPath$ -> NSFileManager$fileExistsAtPath$ 100 | // //#define FUNC_UNGROUPED_NEXT (0 == strcmp(PURE_FUNC, "")) ? (strstr(__func__, UNGROUP_STR) + UNGROUP_LEN) : (PURE_FUNC) 101 | // #define FUNC_UNGROUPED_NEXT IS_EMPTY_STR(PURE_FUNC) ? (strstr(__func__, UNGROUP_STR) + UNGROUP_LEN) : (PURE_FUNC) 102 | 103 | // // NSFileManager$fileExistsAtPath$ -> fileExistsAtPath$ 104 | // // #define FUNC_ONLY_METHOD strchr(FUNC_UNGROUPED_NEXT, '$') ? (strchr(FUNC_UNGROUPED_NEXT, '$') + 1) : __func__ 105 | // // #define FUNC_ONLY_METHOD (NULL != strchr(FUNC_UNGROUPED_NEXT, '$')) ? (strchr(FUNC_UNGROUPED_NEXT, '$') + 1) : __func__ 106 | // #define FUNC_ONLY_METHOD strchr(FUNC_UNGROUPED_NEXT, '$') ? (strchr(FUNC_UNGROUPED_NEXT, '$') + 1) : FUNC_UNGROUPED_NEXT 107 | 108 | 109 | // Method 2: 110 | #define FUNC_NAME_AFTER_UNGROUP strstr(__func__, UNGROUP_STR) ? (strstr(__func__, UNGROUP_STR) + UNGROUP_LEN) : __func__ 111 | // => 112 | // _logos_function$_ungrouped$open -> open 113 | // _logos_method$_ungrouped$NSFileManager$fileExistsAtPath$ -> NSFileManager$fileExistsAtPath$ 114 | // normal_function -> normal_function 115 | 116 | //#define FUNC_NAME strchr(FUNC_NAME_AFTER_UNGROUP, '$') ? (strchr(FUNC_NAME_AFTER_UNGROUP, '$') + 1) : FUNC_NAME_AFTER_UNGROUP 117 | //#define FUNC_NAME_NO_CLASS strchr(FUNC_NAME_AFTER_UNGROUP, '$') ? (strchr(FUNC_NAME_AFTER_UNGROUP, '$') + 1) : FUNC_NAME_AFTER_UNGROUP 118 | // => 119 | // open -> open 120 | // NSFileManager$fileExistsAtPath$ -> fileExistsAtPath$ 121 | // normal_function -> normal_function 122 | 123 | // Updated: add support for `_logos_meta_method` inside hook_aweme.mm 124 | // static BOOL _logos_meta_method$_ungrouped$TTInstallUtil$isJailBroken(_LOGOS_SELF_TYPE_NORMAL Class _LOGOS_SELF_CONST, SEL); 125 | #define FUNC_NAME_NO_CLASS FUNC_NAME_AFTER_UNGROUP 126 | 127 | #define FUNC_NAME strchr(FUNC_NAME_NO_CLASS, ' ') ? (strchr(FUNC_NAME_NO_CLASS, ' ') + 1) : FUNC_NAME_NO_CLASS 128 | // => 129 | // +[CrifanLibHookiOS nsStrListToStr:isSortList:isAddIndexPrefix:] -> nsStrListToStr:isSortList:isAddIndexPrefix:] 130 | 131 | #define HOOK_FILE_NAME strstr(__FILE_NAME__, HOOK_) ? __FILE_NAME__ : (HOOK_ " " __FILE_NAME__) 132 | // => 133 | // hook_aweme.xm -> hook_aweme.xm 134 | // CrifanLibHookiOS.m -> hook_ CrifanLibHookiOS.m 135 | 136 | #define iosLogInfo(format, ...) \ 137 | do { if (IOS_LOG_INFO_ENABLE) os_log(OS_LOG_DEFAULT, "%s %s: " format, HOOK_FILE_NAME, FUNC_NAME, __VA_ARGS__); } while(0) 138 | // do { if (IOS_LOG_INFO_ENABLE) os_log(OS_LOG_DEFAULT, "%s %s: " format, __FILE_NAME__, FUNC_NAME, __VA_ARGS__); } while(0) 139 | // do { if (IOS_LOG_INFO_ENABLE) os_log(OS_LOG_DEFAULT, "%s %s: " format, __FILE_NAME__, FUNC_ONLY_METHOD, __VA_ARGS__); } while(0) 140 | // do { if (IOS_LOG_INFO_ENABLE) os_log(OS_LOG_DEFAULT, "%s %s: " format, __FILE_NAME__, FUNC_UNGROUPED_NEXT, __VA_ARGS__); } while(0) 141 | // do { if (IOS_LOG_INFO_ENABLE) os_log(OS_LOG_DEFAULT, "%s %s: " format, __FILE_NAME__, PURE_FUNC, __VA_ARGS__); } while(0) 142 | // do { if (IOS_LOG_INFO_ENABLE) os_log(OS_LOG_DEFAULT, "%s %s: " format, FILENAME_NO_SUFFIX, PURE_FUNC, __VA_ARGS__); } while(0) 143 | // do { if (IOS_LOG_INFO_ENABLE) os_log(OS_LOG_DEFAULT, "%s %s: " format, __FILE_NAME__, __func__, __VA_ARGS__); } while(0) 144 | 145 | #define iosLogDebug(format, ...) \ 146 | do { if (IOS_LOG_DEBUG_ENABLE) os_log(OS_LOG_DEFAULT, "%s %s: " format, HOOK_FILE_NAME, FUNC_NAME, __VA_ARGS__); } while(0) 147 | // do { if (IOS_LOG_DEBUG_ENABLE) os_log(OS_LOG_DEFAULT, "%s %s: " format, __FILE_NAME__, FUNC_NAME, __VA_ARGS__); } while(0) 148 | // do { if (IOS_LOG_DEBUG_ENABLE) os_log(OS_LOG_DEFAULT, "%s %s: " format, __FILE_NAME__, PURE_FUNC, __VA_ARGS__); } while(0) 149 | 150 | #define iosLogError(format, ...) \ 151 | do { if (IOS_LOG_ERROR_ENABLE) os_log(OS_LOG_DEFAULT, "%s %s: " format, HOOK_FILE_NAME, FUNC_NAME, __VA_ARGS__); } while(0) 152 | // do { if (IOS_LOG_ERROR_ENABLE) os_log(OS_LOG_DEFAULT, "%s %s: " format, __FILE_NAME__, FUNC_NAME, __VA_ARGS__); } while(0) 153 | // do { if (IOS_LOG_ERROR_ENABLE) os_log(OS_LOG_DEFAULT, "%s %s: " format, __FILE_NAME__, PURE_FUNC, __VA_ARGS__); } while(0) 154 | 155 | 156 | NS_ASSUME_NONNULL_BEGIN 157 | 158 | @interface HookLogiOS : NSObject 159 | 160 | @end 161 | 162 | NS_ASSUME_NONNULL_END 163 | 164 | 165 | #endif /* HookLogiOS_h */ 166 | 167 | #ifdef __cplusplus 168 | } 169 | #endif 170 | -------------------------------------------------------------------------------- /iOS/HookLogiOS.m: -------------------------------------------------------------------------------- 1 | /* 2 | File: HookLogiOS.m 3 | Function: crifan's common iOS hook log functions 4 | Author: Crifan Li 5 | Latest: https://github.com/crifan/crifanLib/blob/master/iOS/HookLogiOS.m 6 | Updated: 20241214_1207 7 | */ 8 | 9 | #import "HookLogiOS.h" 10 | #import "CrifanLibiOS.h" 11 | 12 | /*============================================================================== 13 | Global Config 14 | ==============================================================================*/ 15 | 16 | // Note: os_log max length/limit is 1K=1024 17 | // when split large NSString, for single line string length will use this definition 18 | // int LOG_MAX_LEN_ONCE = 1024; 19 | //int LOG_MAX_LEN_ONCE = 1024 - 20; 20 | //int LOG_MAX_LEN_ONCE = 1024 - 50; 21 | //int LOG_MAX_LEN_ONCE = 1024 - 100; 22 | //int LOG_MAX_LEN_ONCE = 1024 - 200; 23 | int LOG_MAX_LEN_ONCE = 1024 - 150; 24 | 25 | 26 | // output one log every N time 27 | //long LOG_ONCE_PER_NUM = 10; 28 | //long LOG_ONCE_PER_NUM = 20; 29 | long LOG_ONCE_PER_NUM = 100; 30 | 31 | /*============================================================================== 32 | Global Variable 33 | ==============================================================================*/ 34 | 35 | long gCurLogNum = 0; 36 | 37 | //only for debug 38 | long gNoUse = 0; 39 | 40 | 41 | /*============================================================================== 42 | Log Utils 43 | ==============================================================================*/ 44 | 45 | bool nonEmptyHeader(id curHeaderDict){ 46 | bool isNonEmpty = false; 47 | if (curHeaderDict != nil){ 48 | id allKeys = [curHeaderDict allKeys]; 49 | long int headerCount = [allKeys count]; 50 | if (headerCount > 0) { 51 | isNonEmpty = true; 52 | } 53 | } 54 | return isNonEmpty; 55 | } 56 | 57 | // log for large (> 1024) string 58 | void logLargeStr(NSString* largeStr){ 59 | // NSMutableArray* splitedLineArr = splitToLines(largeStr, LOG_MAX_LEN_ONCE); 60 | NSMutableArray* splitedLineArr = [CrifanLibiOS splitToLines:largeStr maxLenPerLine:LOG_MAX_LEN_ONCE]; 61 | for(int lineIdx = 0; lineIdx < splitedLineArr.count; lineIdx++){ 62 | NSString* curLineStr = splitedLineArr[lineIdx]; 63 | // os_log(OS_LOG_DEFAULT, "[%d] curLineStr=%@", lineIdx, curLineStr); 64 | // iosLogInfo("[%d] curLineStr=%{public}@", lineIdx, curLineStr); 65 | iosLogInfo("[%d] %{public}@", lineIdx, curLineStr); 66 | } 67 | } 68 | 69 | // log for possible large string 70 | void logPossibleLargeStr(NSString* possibleLargeStr){ 71 | if ([possibleLargeStr length] > LOG_MAX_LEN_ONCE){ 72 | // iosLogInfo("%@", @"log_for_large_str:"); 73 | logLargeStr(possibleLargeStr); 74 | } else { 75 | // iosLogInfo("%@", @"log_for_normal_str:"); 76 | iosLogInfo("%{public}@", possibleLargeStr); 77 | } 78 | } 79 | 80 | void printCallStack(NSString* prefix){ 81 | NSArray *callStackArr = [CrifanLibiOS printCallStack]; 82 | iosLogInfo("%@ -> callStackArr=%{public}@", prefix, callStackArr); 83 | } 84 | 85 | void printCallStack_largeStr(NSString* _Nullable prefix){ 86 | NSArray *callStackArr = [CrifanLibiOS printCallStack]; 87 | NSString* callStackLargeStr = [NSString stringWithFormat:@"%@ -> callStackArr=%@", prefix, callStackArr]; 88 | logPossibleLargeStr(callStackLargeStr); 89 | } 90 | 91 | /*============================================================================== 92 | Debug Functions 93 | ==============================================================================*/ 94 | 95 | // write class description string into file 96 | void dbgWriteClsDescToFile(char* className, id classObj){ 97 | NSString* idNSStr = [classObj _ivarDescription]; 98 | NSString* pdNSStr = [classObj _propertyDescription]; 99 | NSString* mdNSStr = [classObj _methodDescription]; 100 | NSString* smdNSStr = [classObj _shortMethodDescription]; 101 | 102 | const char *idCStr = [idNSStr cStringUsingEncoding:NSUTF8StringEncoding]; 103 | const char *pdCStr = [pdNSStr cStringUsingEncoding:NSUTF8StringEncoding]; 104 | const char *mdCStr = [mdNSStr cStringUsingEncoding:NSUTF8StringEncoding]; 105 | const char *smdCStr = [smdNSStr cStringUsingEncoding:NSUTF8StringEncoding]; 106 | 107 | // const char* smdOutputFile = "/var/root/dev/AADeviceInfo_shortMethodDescription.txt"; 108 | // const char* smdOutputFile = "/var/mobile/AADeviceInfo_shortMethodDescription.txt"; 109 | // const char* outputFilePath = "/var/root/dev"; // failed for no write access 110 | const char* outputFilePath = "/var/mobile"; 111 | // iosLogInfo("outputFilePath=%{public}s", outputFilePath); 112 | // const char* idOutputFile = "AADeviceInfo_ivarDescription.txt"; 113 | // const char* pdOutputFile = "AADeviceInfo_propertyDescription.txt"; 114 | // const char* mdOutputFile = "AADeviceInfo_methodDescription.txt"; 115 | // const char* smdOutputFile = "AADeviceInfo_shortMethodDescription.txt"; 116 | 117 | char idFullPath[200]; 118 | char pdFullPath[200]; 119 | char mdFullPath[200]; 120 | char smdFullPath[200]; 121 | 122 | // snprintf(idFullPath, sizeof(idFullPath), "%s/%s", outputFilePath, idOutputFile); 123 | // snprintf(pdFullPath, sizeof(pdFullPath), "%s/%s", outputFilePath, pdOutputFile); 124 | // snprintf(mdFullPath, sizeof(mdFullPath), "%s/%s", outputFilePath, mdOutputFile); 125 | // snprintf(smdFullPath, sizeof(smdFullPath), "%s/%s", outputFilePath, smdOutputFile); 126 | snprintf(idFullPath, sizeof(idFullPath), "%s/%s_ivarDescription.txt", outputFilePath, className); 127 | snprintf(pdFullPath, sizeof(pdFullPath), "%s/%s_propertyDescription.txt", outputFilePath, className); 128 | snprintf(mdFullPath, sizeof(mdFullPath), "%s/%s_methodDescription.txt", outputFilePath, className); 129 | snprintf(smdFullPath, sizeof(smdFullPath), "%s/%s_shortMethodDescription.txt", outputFilePath, className); 130 | 131 | writeStrToFile(idFullPath, (char*)idCStr); 132 | writeStrToFile(pdFullPath, (char*)pdCStr); 133 | writeStrToFile(mdFullPath, (char*)mdCStr); 134 | writeStrToFile(smdFullPath, (char*)smdCStr); 135 | 136 | // iosLogInfo("Written %s description into %s", className, outputFilePath); 137 | } 138 | 139 | 140 | @implementation HookLogiOS 141 | 142 | @end 143 | -------------------------------------------------------------------------------- /iOS/JailbreakiOS.h: -------------------------------------------------------------------------------- 1 | /* 2 | File: JailbreakiOS.h 3 | Function: crifan's common iOS jailbreak functions 4 | Author: Crifan Li 5 | Latest: https://github.com/crifan/crifanLib/blob/master/iOS/JailbreakiOS.h 6 | Updated: 20220303_1402 7 | */ 8 | 9 | #import 10 | 11 | /*============================================================================== 12 | Define 13 | ==============================================================================*/ 14 | //#define FILE_PREFIX = "file://" 15 | 16 | /*============================================================================== 17 | Const 18 | ==============================================================================*/ 19 | //const char* _Nonnull FILE_PREFIX = "file://"; 20 | extern const char* _Nonnull FILE_PREFIX; 21 | 22 | NS_ASSUME_NONNULL_BEGIN 23 | 24 | @interface JailbreakiOS : NSObject 25 | 26 | /*============================================================================== 27 | Jailbreak Path 28 | ==============================================================================*/ 29 | 30 | + (NSArray *) jbPathList; 31 | + (BOOL) isJailbreakPath_iOS: (NSString*)curPath; 32 | 33 | + (NSArray *) jbDylibList; 34 | + (BOOL) isJbDylib: (NSString*)curPath; 35 | 36 | /*============================================================================== 37 | Phone Type 38 | ==============================================================================*/ 39 | 40 | //+ (NSDictionary*) phoneTypeDict; 41 | //+ (NSMutableArray *) phoneTypeList; 42 | 43 | + (NSArray *) phoneTypeList; 44 | 45 | + (NSString *) getPhoneName:(NSString *)phoneId; 46 | 47 | 48 | @end 49 | 50 | NS_ASSUME_NONNULL_END 51 | -------------------------------------------------------------------------------- /iOS/OpenFileiOS.h: -------------------------------------------------------------------------------- 1 | /* 2 | File: OpenFileiOS.h 3 | Function: crifan's common iOS open file functions header 4 | Author: Crifan Li 5 | Latest: https://github.com/crifan/crifanLib/blob/master/iOS/OpenFileiOS.h 6 | Updated: 20230327_1111 7 | */ 8 | 9 | #import 10 | #import 11 | #import 12 | #import 13 | #import 14 | #import 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | 18 | /*============================================================================== 19 | Exported Global Variable 20 | ==============================================================================*/ 21 | 22 | extern const int OPEN_OK; 23 | extern const int OPEN_FAILED; 24 | 25 | extern const int OPEN_FD_INVALID; 26 | 27 | extern const int ACCESS_OK; 28 | extern const int ACCESS_FAILED; 29 | 30 | extern const int STAT_OK; 31 | extern const int STAT_FAILED; 32 | 33 | extern const int STATFS_OK; 34 | extern const int STATFS_FAILED; 35 | 36 | extern const int FORK_FAILED; 37 | 38 | extern const int PTRACE_OK; 39 | extern const int PTRACE_FAILED; 40 | 41 | extern const int FOPEN_OPEN_FAILED; 42 | 43 | extern const int FCNTL_FAILED; 44 | 45 | //extern const char* REALPATH_FAILED; 46 | extern char* REALPATH_FAILED; 47 | 48 | //extern const char* OPENDIR_FAILED; 49 | //extern char* OPENDIR_FAILED; 50 | //extern const int OPENDIR_FAILED; 51 | //extern int OPENDIR_FAILED; 52 | extern DIR* OPENDIR_FAILED; 53 | 54 | extern const int StrPointerSize; 55 | 56 | extern const int DLADDR_FAILED; 57 | 58 | extern const int DYLD_IMAGE_INDEX_INVALID; 59 | extern const long DYLD_IMAGE_SLIDE_INVALID; 60 | 61 | extern const int SYSCTL_OK; 62 | extern const int SYSCTL_FAIL; 63 | 64 | 65 | /*============================================================================== 66 | Global Type 67 | ==============================================================================*/ 68 | 69 | typedef NS_ENUM(NSInteger, OpenFileFunctionType) { 70 | FUNC_UNKNOWN, 71 | FUNC_STAT, 72 | FUNC_STAT64, 73 | FUNC_SYSCALL_STAT, 74 | FUNC_SYSCALL_STAT64, 75 | FUNC_SVC_0X80_STAT, 76 | FUNC_SVC_0X80_STAT64, 77 | FUNC_OPEN, 78 | FUNC_SYSCALL_OPEN, 79 | FUNC_SVC_0X80_OPEN, 80 | FUNC_FOPEN, 81 | FUNC_NSFILEMANAGER, 82 | FUNC_ACCESS, 83 | FUNC_FACCESSAT, 84 | FUNC_LSTAT, 85 | FUNC_REALPATH, 86 | FUNC_OPENDIR, 87 | FUNC___OPENDIR2, 88 | FUNC_NSURL, 89 | FUNC_STATFS, 90 | FUNC_STATFS64, 91 | FUNC_FSTATFS, 92 | FUNC_FSTATAT, 93 | FUNC_FSTAT, 94 | FUNC_SYSCALL_LSTAT, 95 | FUNC_SYSCALL_FSTAT, 96 | FUNC_SYSCALL_FSTATAT, 97 | FUNC_SYSCALL_STATFS, 98 | FUNC_SYSCALL_FSTATFS, 99 | FUNC_SYSCALL_FOPEN, 100 | FUNC_SYSCALL_ACCESS, 101 | FUNC_SYSCALL_FACCESSAT, 102 | }; 103 | 104 | typedef NS_ENUM(NSInteger, ButtonId) { 105 | BTN_STAT=1, 106 | BTN_STAT64=2, 107 | BTN_SYSCALL_STAT=3, 108 | BTN_SYSCALL_STAT64=4, 109 | BTN_SVC_0X80_STAT=5, 110 | BTN_SVC_0X80_STAT64=6, 111 | BTN_OPEN=7, 112 | BTN_SYSCALL_OPEN=8, 113 | BTN_SVC_0X80_OPEN=9, 114 | BTN_FOPEN=10, 115 | BTN_NSFILEMANAGER=11, 116 | BTN_ACCESS=12, 117 | BTN_FACCESSAT=13, 118 | BTN_LSTAT=14, 119 | BTN_REALPATH=15, 120 | BTN_OPENDIR=16, 121 | BTN___OPENDIR2=17, 122 | BTN_NSURL=18, 123 | BTN_STATFS=19, 124 | BTN_STATFS64=20, 125 | BTN_FSTATFS=21, 126 | BTN_FSTATAT=22, 127 | BTN_FSTAT=23, 128 | BTN_SYSCALL_LSTAT=24, 129 | BTN_SYSCALL_FSTAT=25, 130 | BTN_SYSCALL_FSTATAT=26, 131 | BTN_SYSCALL_STATFS=27, 132 | BTN_SYSCALL_FSTATFS=28, 133 | BTN_SYSCALL_FOPEN=29, 134 | BTN_SYSCALL_ACCESS=30, 135 | BTN_SYSCALL_FACCESSAT=31, 136 | }; 137 | 138 | 139 | /*============================================================================== 140 | Define 141 | ==============================================================================*/ 142 | 143 | 144 | 145 | /*============================================================================== 146 | Const 147 | ==============================================================================*/ 148 | 149 | const int OPEN_OK = 0; 150 | const int OPEN_FAILED = -1; 151 | 152 | const int OPEN_FD_INVALID = -1; 153 | 154 | const int ACCESS_OK = 0; 155 | const int ACCESS_FAILED = -1; 156 | 157 | const int STAT_OK = 0; 158 | const int STAT_FAILED = -1; 159 | 160 | const int STATFS_OK = 0; 161 | const int STATFS_FAILED = -1; 162 | 163 | const int FORK_FAILED = -1; 164 | 165 | const int PTRACE_OK = 0; 166 | const int PTRACE_FAILED = -1; 167 | 168 | const int FOPEN_OPEN_FAILED = NULL; 169 | 170 | const int FCNTL_FAILED = -1; 171 | 172 | //const char* _Nullable REALPATH_FAILED = NULL; 173 | char* _Nullable REALPATH_FAILED = NULL; 174 | 175 | //int OPENDIR_FAILED = NULL; 176 | //const int OPENDIR_FAILED = NULL; 177 | //const char* _Nullable OPENDIR_FAILED = NULL; 178 | //char* _Nullable OPENDIR_FAILED = NULL; 179 | //const DIR* _Nullable OPENDIR_FAILED = NULL; 180 | DIR* OPENDIR_FAILED = NULL; 181 | 182 | const int StrPointerSize = sizeof(const char *); 183 | 184 | const int DLADDR_FAILED = 0; 185 | 186 | const int DYLD_IMAGE_INDEX_INVALID = -1; 187 | const long DYLD_IMAGE_SLIDE_INVALID = 0; 188 | 189 | const int SYSCTL_OK = 0; 190 | const int SYSCTL_FAIL = -1; 191 | 192 | @interface OpenFileiOS : NSObject 193 | 194 | /*============================================================================== 195 | Open File 196 | ==============================================================================*/ 197 | 198 | + (BOOL) openFile:(NSString *)filePath funcType:(OpenFileFunctionType) funcType; 199 | 200 | @end 201 | 202 | NS_ASSUME_NONNULL_END 203 | -------------------------------------------------------------------------------- /java/ByteUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | File: ByteUtil.java 3 | Function: crifan's common java's Bytes related functions 4 | Author: Crifan Li 5 | Latest: https://github.com/crifan/crifanLib/blob/master/java/ByteUtil.java 6 | Updated: 20240807 7 | */ 8 | 9 | public class ByteUtil { 10 | 11 | public static int hexCharToInt(char ch) { 12 | if ('0' <= ch && ch <= '9') { 13 | return ch - '0'; 14 | } 15 | if ('A' <= ch && ch <= 'F') { 16 | return ch - 'A' + 10; 17 | } 18 | if ('a' <= ch && ch <= 'f') { 19 | return ch - 'a' + 10; 20 | } 21 | return -1; 22 | } 23 | 24 | public static byte[] hexStrToBytes(String s) { 25 | final int len = s.length(); 26 | 27 | // "111" is not a valid hex encoding. 28 | if (len % 2 != 0) { 29 | throw new IllegalArgumentException("hexBinary needs to be even-length: " + s); 30 | } 31 | 32 | byte[] parsedBytes = new byte[len / 2]; 33 | 34 | for (int i = 0; i < len; i += 2) { 35 | int h = hexCharToInt(s.charAt(i)); 36 | int l = hexCharToInt(s.charAt(i + 1)); 37 | if (h == -1 || l == -1) { 38 | throw new IllegalArgumentException("contains illegal character for hexBinary: " + s); 39 | } 40 | 41 | parsedBytes[i / 2] = (byte) (h * 16 + l); 42 | } 43 | 44 | return parsedBytes; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /java/CompressionUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | File: CompressionUtil.java 3 | Function: crifan's common java's compress & decompress related functions 4 | Author: Crifan Li 5 | Latest: https://github.com/crifan/crifanLib/blob/master/java/CompressionUtil.java 6 | Updated: 20240808 7 | */ 8 | 9 | import java.io.BufferedReader; 10 | import java.io.ByteArrayInputStream; 11 | import java.io.ByteArrayOutputStream; 12 | import java.io.IOException; 13 | import java.io.InputStreamReader; 14 | import java.nio.charset.StandardCharsets; 15 | import java.util.zip.GZIPInputStream; 16 | import java.util.zip.GZIPOutputStream; 17 | 18 | public class CompressionUtil { 19 | 20 | public static boolean isCompressed(final byte[] compressed) { 21 | return (compressed[0] == (byte) (GZIPInputStream.GZIP_MAGIC)) && 22 | (compressed[1] == (byte) (GZIPInputStream.GZIP_MAGIC >> 8)); 23 | } 24 | 25 | public static byte[] compress(final String str) throws IOException, IOException { 26 | if ((str == null) || (str.length() == 0)) { 27 | return null; 28 | } 29 | 30 | ByteArrayOutputStream byteArrOutStream = new ByteArrayOutputStream(); 31 | GZIPOutputStream gzipOutStream = new GZIPOutputStream(byteArrOutStream); 32 | gzipOutStream.write(str.getBytes(StandardCharsets.UTF_8)); 33 | gzipOutStream.flush(); 34 | gzipOutStream.close(); 35 | return byteArrOutStream.toByteArray(); 36 | } 37 | 38 | public static String decompressToStr(final byte[] compressed) throws IOException { 39 | final StringBuilder outStr = new StringBuilder(); 40 | if ((compressed == null) || (compressed.length == 0)) { 41 | return ""; 42 | } 43 | if (isCompressed(compressed)) { 44 | final GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(compressed)); 45 | final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(gis, StandardCharsets.UTF_8)); 46 | 47 | String line; 48 | while ((line = bufferedReader.readLine()) != null) { 49 | outStr.append(line); 50 | } 51 | } else { 52 | outStr.append(compressed); 53 | } 54 | return outStr.toString(); 55 | } 56 | 57 | public static byte[] decompressToBytes(byte[] gzipBytes) throws IOException { 58 | // With 'gzip' being the compressed buffer 59 | ByteArrayInputStream inputBytes = new ByteArrayInputStream(gzipBytes); 60 | GZIPInputStream inputGzipStream = new GZIPInputStream(inputBytes); 61 | ByteArrayOutputStream outputBytes = new ByteArrayOutputStream(); 62 | 63 | int res = 0; 64 | int bufSize = 512 * 1024; 65 | byte buf[] = new byte[bufSize]; 66 | while (res >= 0) { 67 | res = inputGzipStream.read(buf, 0, buf.length); 68 | if (res > 0) { 69 | outputBytes.write(buf, 0, res); 70 | } 71 | } 72 | byte uncompressedBytes[] = outputBytes.toByteArray(); 73 | return uncompressedBytes; 74 | } 75 | 76 | public static String decodeGzipHexStr(String gzipHexStr) throws IOException { 77 | byte[] bodyBytes = ByteUtil.hexStrToBytes(gzipHexStr); 78 | Utils.logD(String.format("bodyBytes=%s", bodyBytes)); 79 | byte[] uncompressedBodyBytes = CompressionUtil.decompressToBytes(bodyBytes); 80 | Utils.logD(String.format("uncompressedBodyBytes=%s", uncompressedBodyBytes)); 81 | String decodedStr = new String(uncompressedBodyBytes, StandardCharsets.UTF_8); 82 | Utils.logD(String.format("decodedStr=%s", decodedStr)); 83 | return decodedStr; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /java/ListUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | File: ListUtil.java 3 | Function: crifan's common java's List related functions 4 | Author: Crifan Li 5 | Latest: https://github.com/crifan/crifanLib/blob/master/java/ListUtil.java 6 | Updated: 20240807 7 | */ 8 | 9 | import java.util.List; 10 | 11 | public class ListUtil { 12 | 13 | // for [a, b, c] and [c, b, a, b], here isListEqual -> true = means equal 14 | // if you think is not equal -> then should modify isListEqual's code logic 15 | public static boolean isListEqual(List list1, List list2){ 16 | // boolean isEqual = CollectionUtils.isEqualCollection(list1, list2)); 17 | boolean isEqual = list1.containsAll(list2) && list2.containsAll(list1); 18 | return isEqual; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /java/UrlUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | File: UrlUtil.java 3 | Function: crifan's common java's url related functions 4 | Author: Crifan Li 5 | Latest: https://github.com/crifan/crifanLib/blob/master/java/UrlUtil.java 6 | Updated: 20240807 7 | */ 8 | 9 | import java.io.UnsupportedEncodingException; 10 | import java.net.URI; 11 | import java.net.URISyntaxException; 12 | import java.net.URLDecoder; 13 | import java.nio.charset.StandardCharsets; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | public class UrlUtil { 18 | 19 | /** 20 | * Parse out query string map/dict from url 21 | * @param url url 22 | * @return query string map/dict 23 | */ 24 | public static Map parseUrlQsPara(String url) throws URISyntaxException, UnsupportedEncodingException { 25 | // Utils.logD(String.format("parseUrlQsPara: url=%s", url)); 26 | Map urlQsParaDict = new HashMap(); 27 | URI uri = new URI(url); 28 | // Utils.logD(String.format("uri=%s", uri)); 29 | String scheme = uri.getScheme(); 30 | String host = uri.getHost(); 31 | // Utils.logD(String.format("scheme=%s, host=%s", scheme, host)); 32 | String rawQuery = uri.getRawQuery(); 33 | // Utils.logD(String.format("rawQuery=%s", rawQuery)); 34 | // String decodedQuery = null; 35 | // if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) { 36 | // decodedQuery = URLDecoder.decode(rawQuery, StandardCharsets.UTF_8); 37 | // } 38 | String decodedQuery = URLDecoder.decode(rawQuery, StandardCharsets.UTF_8.toString()); 39 | // Utils.logD(String.format("decodedQuery=%s", decodedQuery)); 40 | // String[] paraList = rawQuery.split("&"); 41 | String[] paraList = decodedQuery.split("&"); 42 | // Utils.logD(String.format("paraList=%s", paraList.toString())); 43 | for (String eachPart : paraList) { 44 | // Utils.logD(String.format("eachPart=%s", eachPart)); 45 | String paraKey = ""; 46 | String paraValue = ""; 47 | if (eachPart.contains("=")){ 48 | String[] paraKeyValueList = eachPart.split("="); 49 | paraKey = paraKeyValueList[0]; 50 | paraValue = paraKeyValueList[1]; 51 | } else { 52 | paraKey = eachPart; 53 | paraValue = ""; 54 | } 55 | // Utils.logD(String.format("paraKey=%s, paraValue=%s", paraKey, paraValue)); 56 | urlQsParaDict.put(paraKey, paraValue); 57 | } 58 | // Utils.logD(String.format("urlQsParaDict=%s", urlQsParaDict)); 59 | return urlQsParaDict; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /java/crifanLibOkHttp.java: -------------------------------------------------------------------------------- 1 | /* 2 | File: crifanLibOkHttp.java 3 | Function: crifan's common java's OkHttp network related functions 4 | Author: Crifan Li 5 | Latest: https://github.com/crifan/crifanLib/blob/master/java/crifanLibOkHttp.java 6 | Updated: 20240807 7 | */ 8 | 9 | import okhttp3.Call; 10 | import okhttp3.Callback; 11 | import okhttp3.FormBody; 12 | import okhttp3.MediaType; 13 | import okhttp3.MultipartBody; 14 | import okhttp3.OkHttpClient; 15 | import okhttp3.Request; 16 | import okhttp3.RequestBody; 17 | import okhttp3.Response; 18 | import okhttp3.ResponseBody; 19 | 20 | import crifanLib; 21 | 22 | public class crifanLibOkHttp { 23 | 24 | public crifanLibOkHttp() 25 | { 26 | } 27 | 28 | /** 29 | * do/execute http POST request, body format is JSON (not Form) 30 | * @param url url 31 | * @param bodyJsonStr post body string 32 | * @return the response 33 | */ 34 | public static Response doPost(String url, String bodyJsonStr) { 35 | Response response = null; 36 | // Utils.logD(String.format("doPost: url=%s, bodyJsonStr=%s", url, bodyJsonStr)); 37 | RequestBody reqBody = RequestBody.create(MEDIA_TYPE_JSON, bodyJsonStr); 38 | // Utils.logD(String.format("reqBody=%s", reqBody)); 39 | Request request = new Request.Builder() 40 | .url(url) 41 | .post(reqBody) 42 | .build(); 43 | // Utils.logD(String.format("request=%s", request)); 44 | try { 45 | response = client.newCall(request).execute(); 46 | } catch (IOException err) { 47 | // Utils.logE(String.format("err=%s for POST url=%s", err, url)); 48 | } 49 | // Utils.logD(String.format("response=%s", response)); 50 | return response; 51 | } 52 | 53 | /** 54 | * do/execute http POST request, body format is JSON (not Form) 55 | * @param url url 56 | * @param paramDict body parameter dict/map 57 | * @return the response 58 | */ 59 | public static Response doPost(String url, Map paramDict) { 60 | // Utils.logD(String.format("doPost: url=%s, paramDict=%s", url, paramDict)); 61 | String postJsonStr = JsonMapUtil.mapToJsonStr(paramDict); 62 | // Utils.logD(String.format("postJsonStr=%s", postJsonStr)); 63 | return doPost(url, postJsonStr); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /java/crifanLibSongtaste.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [File] 3 | * crifanLibSongtaste.java 4 | * 5 | * [Function] 6 | * 1. implement common songtaste functions 7 | * 8 | * [Version] 9 | * v1.0 10 | * 2013-04-27 11 | * 12 | * [History] 13 | * 1. add extract title, singer, real address 14 | */ 15 | 16 | package crifan.com; 17 | 18 | import java.io.File; 19 | import java.util.regex.Matcher; 20 | import java.util.regex.Pattern; 21 | import java.util.List; 22 | import java.util.ArrayList; 23 | 24 | import org.apache.http.NameValuePair; 25 | import org.apache.http.params.BasicHttpParams; 26 | import org.apache.http.params.HttpParams; 27 | import org.apache.http.message.BasicNameValuePair; 28 | 29 | import crifan.com.crifanLib.UpdateProgressCallback; 30 | 31 | public class crifanLibSongtaste { 32 | public class albumInfo 33 | { 34 | public String url; 35 | public String name; 36 | public String author; 37 | }; 38 | 39 | public class songInfo { 40 | public String id; // 2224853 41 | public String url; // http://www.songtaste.com/song/2224853/ 42 | public String realAddr; // http://m6.songtaste.com/201204131040/90ee3460a764e82816d5233fe2acaccc/6/62/625c4102d5a2f89f614d874b2c2ca402.mp3 43 | public String artist; // DJ OKAWARI 44 | public String title; // Flower Dance 45 | public String suffix; // .mp3 46 | public String storedName; //Flower Dance - DJ OKAWARI.mp3 47 | 48 | //public string recommender;// loveqian1314 49 | //public string recommenderId;// 3334687 50 | //public string recommenderUrl;// http://songtaste.com/user/3334687/ 51 | 52 | songInfo() 53 | { 54 | id = ""; 55 | url = ""; 56 | realAddr = ""; 57 | artist = ""; 58 | title = ""; 59 | suffix = ""; 60 | storedName = ""; 61 | } 62 | }; 63 | 64 | crifanLib crifanLib = null; 65 | 66 | /* 67 | * GB18030 is superset of GBK 68 | * GBK is superset of GB2312 69 | * */ 70 | static final String stHtmlCharset = "GB18030"; 71 | 72 | public crifanLibSongtaste() 73 | { 74 | 75 | crifanLib = new crifanLib(); 76 | 77 | }; 78 | 79 | 80 | /** 81 | * None pattern version of extractSingleStr 82 | * */ 83 | public Boolean extractSingleStr(String pattern, String extractFrom, StringBuilder extractedStr) 84 | { 85 | return crifanLib.extractSingleStr(pattern, extractFrom, 0, extractedStr); 86 | } 87 | 88 | /** Extract music title from html of songtaste music url */ 89 | public Boolean stExtractMusicTitle(String html, StringBuilder sbExtractedTitle) 90 | { 91 | return crifanLib.extractSingleStr("(.+?)

", html, sbExtractedTitle); 92 | } 93 | 94 | /** 95 | * Extract music singer from html of songtaste music url 96 | * Note: special: http://www.songtaste.com/song/809103/, no singer, got empty string 97 | * */ 98 | public Boolean stExtractMusicSinger(String html, StringBuilder sbExtractedSinger) 99 | { 100 | return crifanLib.extractSingleStr("(.*?)", html, sbExtractedSinger); 101 | } 102 | 103 | /** get real music address from songtaste music url */ 104 | public Boolean stGetRealAddressFromUrl(String stUrl, StringBuilder sbMusicRealAddress) { 105 | Boolean gotReadAddr = Boolean.FALSE; 106 | String realAddr = ""; 107 | 108 | String respHtml = crifanLib.getUrlRespHtml(stUrl, stHtmlCharset); 109 | 110 | // 1. extract title 111 | //

我的爱与你分享

112 | StringBuilder sbExtractedTitle = new StringBuilder(); 113 | if(stExtractMusicTitle(respHtml, sbExtractedTitle)) 114 | { 115 | //System.out.println(sbExtractedTitle); 116 | 117 | // 2. extract singer 118 | //

未知

119 | StringBuilder sbExtractedSinger = new StringBuilder(); 120 | if(stExtractMusicSinger(respHtml, sbExtractedSinger)) 121 | { 122 | String extractedSinger = sbExtractedSinger.toString(); 123 | //special: http://www.songtaste.com/song/809103/, no singer 124 | if(extractedSinger == "") 125 | { 126 | extractedSinger = "Unknown Singer"; 127 | } 128 | 129 | // 3. get song real address 130 | // 131 | //special http://www.songtaste.com/song/2428041/ contain: 132 | // 133 | String mediaPatStr = "javascript:playmedia1\\('playicon','player', '([^']+)', '\\d+', '\\d+', '(\\w+)', '(.+?)', '(\\d+)',\\d+\\);"; 134 | Pattern mediaP = Pattern.compile(mediaPatStr); 135 | Matcher foundMedia = mediaP.matcher(respHtml); 136 | Boolean bFoundMedia = foundMedia.find(); 137 | if(bFoundMedia) 138 | { 139 | String str = foundMedia.group(1); 140 | String urlPref = foundMedia.group(2); 141 | String keyStr = foundMedia.group(3); 142 | String sid = foundMedia.group(4); 143 | 144 | if(str.contains("/")) 145 | { 146 | //cachefile33.rayfile.com/12f1/zh-cn/download/d1e8d86a0a9880f697aee789f27383db/preview 147 | //to get the suffix 148 | String suffix = ""; 149 | String mainJsUrl = "http://image.songtaste.com/inc/main.js"; 150 | String respHtmlMainJs = crifanLib.getUrlRespHtml(mainJsUrl); 151 | // case "b3a7a4e64bcd8aabe4cabe0e55b57af5": 152 | // return ".mp3"; 153 | String suffixPatStr = "\"" + keyStr + "\":.+?return\\s+\"(\\.\\w+)\";"; 154 | 155 | // Pattern suffixP = Pattern.compile(suffixPatStr, Pattern.DOTALL); 156 | // Matcher foundSuffix = suffixP.matcher(respHtmlMainJs); 157 | // Boolean bFoundSuffix = foundMedia.find(); 158 | // if(bFoundSuffix) 159 | // { 160 | // suffix = foundSuffix.group(1); 161 | // } 162 | 163 | StringBuilder sbExtractedSuffix= new StringBuilder(); 164 | if(crifanLib.extractSingleStr(suffixPatStr, respHtmlMainJs, Pattern.DOTALL, sbExtractedSuffix)) 165 | { 166 | suffix = sbExtractedSuffix.toString(); 167 | realAddr = urlPref + str + suffix; 168 | } 169 | } 170 | else 171 | { 172 | //5bf271ccad05f95186be764f725e9aaf07e0c7791a89123a9addb2a239179e64c91834c698a9c5d82f1ced3fe51ffc51 173 | 174 | //header 175 | HttpParams headrDict = new BasicHttpParams(); 176 | headrDict.setParameter("x-requested-with", "XMLHttpRequest"); 177 | 178 | //post data 179 | List postDict = new ArrayList(); 180 | postDict.add(new BasicNameValuePair("str", str)); 181 | postDict.add(new BasicNameValuePair("sid", sid)); 182 | postDict.add(new BasicNameValuePair("t", "0")); 183 | String getRealAddrUrl = "http://songtaste.com/time.php"; 184 | realAddr = crifanLib.getUrlRespHtml(getRealAddrUrl, headrDict, stHtmlCharset, postDict); 185 | } 186 | }//bFoundMedia 187 | }//stExtractMusicSinger 188 | }//stExtractMusicTitle 189 | 190 | if(realAddr != ""){ 191 | sbMusicRealAddress.append(realAddr); 192 | gotReadAddr = Boolean.TRUE; 193 | } 194 | 195 | return gotReadAddr; 196 | } 197 | 198 | public Boolean stDownloadFromUrl(String strSongUrl, File fullFilename, UpdateProgressCallback updateProgressCallbak) 199 | { 200 | Boolean downloadOk = Boolean.FALSE; 201 | 202 | StringBuilder sbMusicRealAddress = new StringBuilder(); 203 | if(stGetRealAddressFromUrl(strSongUrl, sbMusicRealAddress)) 204 | { 205 | HttpParams headrDict = new BasicHttpParams(); 206 | headrDict.setParameter("Referer", "http://songtaste.com/"); 207 | 208 | crifanLib.downlodFile(sbMusicRealAddress.toString(), fullFilename, headrDict, updateProgressCallbak); 209 | } 210 | 211 | return downloadOk; 212 | } 213 | 214 | } -------------------------------------------------------------------------------- /javascript/common.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Javascript common functions 3 | * 4 | * Author: Crifan Li 5 | * Updated: 20180808 6 | * 7 | */ 8 | 9 | // range(10, 0, -1) 10 | // range(0, 5, 1) 11 | export function range(start, stop, step) { 12 | // console.log(`range: start=${start}, stop=${stop}, step=${step}`) 13 | 14 | let realStop = stop 15 | let realStart = start 16 | let realStep = step 17 | 18 | if (typeof realStop === 'undefined') { 19 | // one param defined 20 | realStop = realStart 21 | realStart = 0 22 | } 23 | 24 | if (typeof realStep === 'undefined') { 25 | realStep = 1 26 | } 27 | // console.log(`range: start=${start}, stop=${stop}, step=${step}`) 28 | 29 | if ((realStep > 0 && realStart >= realStop) || (realStep < 0 && realStart <= realStop)) { 30 | // return [] 31 | return [realStart] 32 | } 33 | 34 | const result = [] 35 | for (let i = realStart; realStep > 0 ? i < realStop : i > realStop; i += realStep) { 36 | result.push(i) 37 | } 38 | 39 | return result 40 | } 41 | -------------------------------------------------------------------------------- /javascript/datetime.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Javascript datetime realted functions 3 | * 4 | * Author: Crifan Li 5 | * Updated: 20181122 6 | * 7 | */ 8 | 9 | export const INVALID_DATE = new Date("1970/1/1"); 10 | 11 | 12 | //from "1500422400000" or 1500422400000 to Wed Jul 19 2017 08:00:00 GMT+0800 (CST) 13 | export function timestampToDate(timestamp) { 14 | let convertedDate = null; 15 | 16 | let timestampInt = 0; 17 | if (typeof(timestamp) === "number") { 18 | timestampInt = timestamp; 19 | } else if (typeof(timestamp) === "string"){ 20 | timestampInt = parseInt(timestamp, 10); 21 | } 22 | 23 | if (timestampInt > 0) { 24 | convertedDate = new Date(timestampInt); 25 | } 26 | 27 | // console.log(`timestamp=${timestamp} -> ${convertedDate}`); 28 | return convertedDate; 29 | } 30 | 31 | // from a date to "2017-06-30" 32 | export function datetimeToStr(datetime, nullStr="", formatStr="yyyy-MM-dd"){ 33 | // console.log(`datetime=${datetime},nullStr=${nullStr},formatStr=${formatStr}`); 34 | 35 | if ((datetime === null) || (datetime === INVALID_DATE)) 36 | return nullStr; 37 | 38 | const formatedDate = datetime.Format(formatStr); 39 | 40 | // console.log(`datetime=${datetime},nullStr=${nullStr},formatStr=${formatStr} -> formatedDate=${formatedDate}`); 41 | return formatedDate; 42 | } 43 | 44 | 45 | //from "2017-02-22" to Date() 46 | export function strToDatetime(datetimeStr){ 47 | if ((datetimeStr === "") || (datetimeStr === null)) 48 | return null; 49 | 50 | let convertedDate = new Date(datetimeStr); 51 | 52 | // console.log(`datetimeStr=${datetimeStr} -> convertedDate=${convertedDate}`); 53 | return convertedDate; 54 | } 55 | 56 | // 对Date的扩展,返回之前或之后的多少个小时的时间 57 | // 例子: 58 | // (new Date()).prevNextHours(5) / (new Date()).prevNextHours(-7) 59 | // ==> 60 | // current date [Tue Jun 06 2017 08:00:00 GMT+0800 (CST)] + [7] hours -> [Tue Jun 06 2017 15:00:00 GMT+0800 (CST)] 61 | Date.prototype.prevNextHours = function (hoursNumber) { 62 | // console.log(`Date prevNextHours: this=${this}, hoursNumber=${hoursNumber}`); 63 | 64 | // Note: here only got date pointer(reference) not date value, so later setHours will change this !!! 65 | // let tmpDate = this; 66 | let tmpDate = new Date(this); 67 | let changedDateTimestamp = tmpDate.setHours(tmpDate.getHours() + hoursNumber); 68 | // console.log(`changedDateTimestamp=${changedDateTimestamp}`); 69 | let changedDate = new Date(changedDateTimestamp); 70 | 71 | // console.log(`current date [${this}] + [${hoursNumber}] hours -> [${changedDate}]`); 72 | return changedDate; 73 | }; 74 | 75 | // 对Date的扩展,返回时间戳(毫秒,13位) 76 | // 例子: 77 | // this=Tue Jun 06 2017 15:00:00 GMT+0800 (CST) -> 1496732400000 78 | // Mon Nov 12 2018 11:03:47 GMT+0800 (中国标准时间) -> 1541991827756 79 | Date.prototype.timestamp = function () { 80 | // console.log(`Date timesamp: this=${this}`); 81 | let timesamp = this.getTime(); 82 | // console.log(`this=${this} -> timesamp=${timesamp}`); 83 | return timesamp 84 | } 85 | // console.debug("=== Test Date.timestamp ===") 86 | // var curDate = new Date() 87 | // console.log("curDate=%o", curDate) 88 | // var curTimestamp = curDate.timestamp() 89 | // console.log("curTimestamp=%o", curTimestamp) 90 | 91 | 92 | // 对Date的扩展,将 Date 转化为指定格式的String 93 | // 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符, 94 | // 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字) 95 | // 例子: 96 | // (new Date()).Format("yyyy-MM-dd HH:mm:ss.S") ==> 2006-07-02 08:09:04.423 97 | // (new Date()).Format("yyyy-M-d h:H:s.S") ==> 2006-7-2 8:9:4.18 98 | Date.prototype.Format = function (fmt) { 99 | const o = { 100 | "M+": this.getMonth() + 1, //月份 101 | "d+": this.getDate(), //日 102 | "H+": this.getHours(), //小时 103 | "m+": this.getMinutes(), //分 104 | "s+": this.getSeconds(), //秒 105 | "q+": Math.floor((this.getMonth() + 3) / 3), //季度 106 | "S": this.getMilliseconds() //毫秒 107 | }; 108 | 109 | if (/(y+)/.test(fmt)){ 110 | fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); 111 | } 112 | 113 | for (const k in o){ 114 | if (new RegExp("(" + k + ")").test(fmt)){ 115 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); 116 | } 117 | } 118 | return fmt; 119 | } 120 | // console.debug("=== Test Date.Format ===") 121 | // var curDate = new Date() 122 | // console.log("curDate=%o", curDate) 123 | // var curDateStr1 = curDate.Format("yyyy-MM-dd") //2017-06-09 124 | // var curDateStr2 = curDate.Format("yyyy-MM-dd HH:mm:ss") //2017-06-09 09:54:35 125 | // var curDateStr3 = curDate.Format("yyyyMMdd_HHmmss") //20170609_095435 126 | // console.log("curDateStr1=%o", curDateStr1) 127 | // console.log("curDateStr2=%o", curDateStr2) 128 | // console.log("curDateStr3=%o", curDateStr3) 129 | 130 | 131 | /** * 对Date的扩展,将 Date 转化为指定格式的String * 月(M)、日(d)、12小时(h)、24小时(H)、分(m)、秒(s)、周(E)、季度(q) 132 | 可以用 1-2 个占位符 * 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字) 133 | * eg: 134 | * (new Date()).pattern("yyyy-MM-dd HH:mm:ss.S")==> 2006-07-02 08:09:04.423 135 | * (new Date()).pattern("yyyy-MM-dd E HH:mm:ss") ==> 2009-03-10 二 20:09:04 136 | * (new Date()).pattern("yyyy-MM-dd EE HH:mm:ss") ==> 2009-03-10 周二 08:09:04 137 | * (new Date()).pattern("yyyy-MM-dd EEE HH:mm:ss") ==> 2009-03-10 星期二 08:09:04 138 | * (new Date()).pattern("yyyy-M-d H:m:s.S") ==> 2006-7-2 8:9:4.18 139 | */ 140 | Date.prototype.pattern=function(fmt) { 141 | const o = { 142 | "M+" : this.getMonth()+1, //月份 143 | "d+" : this.getDate(), //日 144 | "h+" : this.getHours()%12 == 0 ? 12 : this.getHours()%12, //小时 145 | "H+" : this.getHours(), //小时 146 | "m+" : this.getMinutes(), //分 147 | "s+" : this.getSeconds(), //秒 148 | "q+" : Math.floor((this.getMonth()+3)/3), //季度 149 | "S" : this.getMilliseconds() //毫秒 150 | }; 151 | 152 | if (/(y+)/.test(fmt)){ 153 | fmt = fmt.replace(RegExp.$1, (this.getFullYear()+"").substr(4 - RegExp.$1.length)); 154 | } 155 | 156 | if (/(E+)/.test(fmt)){ 157 | fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length>1) ? (RegExp.$1.length>2 ? "星期" : "周") : "") + "日一二三四五六".charAt(this.getDay())); 158 | } 159 | 160 | for (const k in o){ 161 | if (new RegExp("("+ k +")").test(fmt)){ 162 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length))); 163 | } 164 | } 165 | 166 | return fmt; 167 | } 168 | // console.debug("=== Test Date.pattern ===") 169 | // var curDate = new Date() 170 | // console.log(curDate.pattern("yyyy-MM-dd EEE HH:mm:ss")) //2017-06-09 星期五 10:16:12 171 | // console.log(curDate.pattern("yyyy-MM-dd EE HH:mm:ss")) //2017-06-09 周五 10:16:12 172 | // console.log(curDate.pattern("yyyy-MM-dd E HH:mm:ss")) //2017-06-09 五 10:16:12 173 | 174 | -------------------------------------------------------------------------------- /javascript/dict.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Javascript Dict related functions 3 | * 4 | * Author: Crifan Li 5 | * Updated: 20181122 6 | * 7 | */ 8 | 9 | 10 | // input: {book_id: "5bd7bd51bfaa44fe2c737c78"}, "book_id" 11 | // output: [true, "5bd7bd51bfaa44fe2c737c78"] 12 | function getValueFromDict(dictObj, keyName){ 13 | console.log("getValueFromDict: dictObj=%o, keyName=%s", dictObj, keyName) 14 | 15 | var getOk = false 16 | var gotValue = undefined 17 | if (dictObj) { 18 | var dictKeys = Object.keys(dictObj) 19 | console.log("dictKeys=%o", dictKeys) 20 | if (dictKeys.includes(keyName)){ 21 | getOk = true 22 | gotValue = dictObj[keyName] 23 | } 24 | } 25 | 26 | console.log("getOk=%s, gotValue=%s", getOk, gotValue) 27 | return [getOk, gotValue] 28 | } 29 | -------------------------------------------------------------------------------- /javascript/frida/README.md: -------------------------------------------------------------------------------- 1 | * 20241211 2 | * Frida related utils, has moved to: 3 | * https://github.com/crifan/JsFridaUtil 4 | -------------------------------------------------------------------------------- /javascript/list.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Javascript List related functions 3 | * 4 | * Author: Crifan Li 5 | * Updated: 20181201 6 | * 7 | */ 8 | 9 | 10 | function removeListItem(originList, itemInList) { 11 | // console.log("removeListItem: originList=%o, itemInList=%o", originList, itemInList) 12 | var foundIndex = originList.indexOf(itemInList) 13 | // console.log("foundIndex=%s", foundIndex) 14 | if (foundIndex >= 0){ 15 | originList.splice(foundIndex, 1) 16 | } 17 | // console.log("originList=%o", originList) 18 | return originList 19 | } 20 | -------------------------------------------------------------------------------- /javascript/storage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Javascript storage realted functions 3 | * 4 | * Author: Crifan Li 5 | * Updated: 20180802 6 | * 7 | */ 8 | 9 | /* 10 | Save js object to local storage via json string 11 | call example: localStorage.setObj("currentUser", currentUser) 12 | */ 13 | Storage.prototype.setObj = function(strKey, objValue) { 14 | // console.log("Storage.prototype.setOb: strKey=", strKey, ", objValue=", objValue) 15 | const jsonStr = JSON.stringify(objValue) 16 | // console.log("jsonStr=", jsonStr) 17 | return this.setItem(strKey, jsonStr) 18 | } 19 | 20 | /* 21 | Restore js object from local storage's stored json string 22 | call example: restoredCurrentUser = localStorage.getObj("currentUser") 23 | */ 24 | Storage.prototype.getObj = function(strKey) { 25 | // console.log("Storage.prototype.getObj: strKey=", strKey) 26 | let parsedObj 27 | const storedStr = this.getItem(strKey) 28 | // console.log("storedStr=", storedStr) 29 | if (storedStr) { 30 | parsedObj = JSON.parse(storedStr) 31 | } 32 | // console.log("parsedObj=", parsedObj) 33 | return parsedObj 34 | } 35 | -------------------------------------------------------------------------------- /javascript/string.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Javascript string realted functions 3 | * 4 | * Author: Crifan Li 5 | * Updated: 20180808 6 | * 7 | */ 8 | 9 | // extract single sub string from full string 10 | // eg: extract '012345678912345' from 'www.ucows.cn/qr?id=012345678912345' 11 | export function extractSingleStr(curStr, pattern, flags='i') { 12 | let extractedStr = null; 13 | 14 | let rePattern = new RegExp(pattern, flags); 15 | console.log(rePattern); 16 | 17 | let matches = rePattern.exec(curStr); 18 | console.log(matches); 19 | 20 | if (matches) { 21 | extractedStr = matches[0]; 22 | console.log(extractedStr); 23 | } 24 | 25 | if (extractedStr === null) { 26 | extractedStr = ""; 27 | } 28 | 29 | console.log(`curStr=${curStr}, pattern=${pattern}, flags=${flags} -> extractedStr=${extractedStr}`); 30 | 31 | return extractedStr; 32 | } 33 | 34 | // let capitalizedStr = originStr.capitalize() 35 | // 'hello' -> 'Hello' 36 | String.prototype.capitalize = function() { 37 | return this.charAt(0).toUpperCase() + this.slice(1); 38 | } 39 | 40 | // add String.format support 41 | if (!String.format) { 42 | String.format = function(format) { 43 | var args = Array.prototype.slice.call(arguments, 1); 44 | return format.replace(/{(\d+)}/g, function(match, number) { 45 | return typeof args[number] != 'undefined' 46 | ? args[number] 47 | : match 48 | ; 49 | }); 50 | }; 51 | } 52 | // console.debug("=== Test String format:") 53 | // var int1 = 123 54 | // var str2 = "abc" 55 | // var float3 = 4.56 56 | // var formatedStr = String.format("int1={0}\nstr2={1}\tfloat3={2}", int1, str2, float3); 57 | // console.log("formatedStr=%s", formatedStr) 58 | // formatedStr=int1=123 59 | // str2=abc float3=4.56 -------------------------------------------------------------------------------- /javascript/type.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Javascript type check realted functions 3 | * 4 | * Author: Crifan Li 5 | * Updated: 20181122 6 | * 7 | */ 8 | 9 | export function isEmptyObj(obj) { 10 | // function isEmptyObj(obj) { 11 | var isEmpty = false 12 | if (obj === undefined){ 13 | isEmpty = true 14 | } else { 15 | isEmpty = (Object.keys(obj).length === 0) 16 | } 17 | return isEmpty 18 | } 19 | // console.debug("=== test empty object ===") 20 | // var emptyObj = {} 21 | // var undefinedObj = undefined 22 | // console.log("emptyObj isEmpty=", isEmptyObj(emptyObj)) // true 23 | // console.log("undefinedObj isEmpty=", isEmptyObj(undefinedObj)) // true 24 | 25 | 26 | export function isString(curValue) { 27 | const isStr = (typeof curValue === 'string' || curValue instanceof String) 28 | return isStr 29 | } 30 | 31 | export function isNumber(curValue) { 32 | const isNum = (typeof curValue === 'number' || curValue instanceof Number) 33 | return isNum 34 | } 35 | 36 | export function isBoolean(curValue) { 37 | const isBool = (typeof curValue === 'boolean' || curValue instanceof Boolean) 38 | return isBool 39 | } 40 | -------------------------------------------------------------------------------- /javascript/url.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Javascript URL/http related functions 3 | * 4 | * Author: Crifan Li 5 | * Updated: 20181122 6 | * 7 | */ 8 | 9 | 10 | // input: q=&tag=Shapes%20and%20Sizes&difficulty=2 11 | // output: {q: "", tag: "Shapes and Sizes", difficulty: "2"} 12 | function decodeQueryStr(encodedQueryStr) { 13 | console.log("decodeQueryStr: encodedQueryStr=%s", encodedQueryStr) 14 | var decodedQueryDict = {} 15 | 16 | if (encodedQueryStr){ 17 | var keyValuePairList = encodedQueryStr.split('&') 18 | console.log("keyValuePairList=%o", keyValuePairList) 19 | 20 | for (keyValuePair of keyValuePairList){ 21 | console.log("keyValuePair=%s", keyValuePair) 22 | var keyValueList = keyValuePair.split("=") 23 | console.log("keyValueList=%o", keyValueList) 24 | var curKey = keyValueList[0] 25 | console.log("curKey=%s", curKey) 26 | if (curKey){ 27 | var curValue = keyValueList[1] 28 | console.log("curValue=%s", curValue) 29 | var decodedKey = decodeURIComponent(curKey) 30 | var decodedValue = decodeURIComponent(curValue) 31 | console.log("decodedKey=%s, decodedValue=%s", decodedKey, decodedValue) 32 | 33 | decodedQueryDict[decodedKey] = decodedValue 34 | } 35 | } 36 | } 37 | 38 | console.log("decodedQueryDict=%o", decodedQueryDict) 39 | return decodedQueryDict 40 | } 41 | 42 | 43 | // input: {q: "", tag: "Shapes and Sizes", difficulty: 2} 44 | // output: q=&tag=Shapes%20and%20Sizes&difficulty=2 45 | function encodeQueryDict(queryDict){ 46 | console.log("encodeQueryDict: queryDict=%o", queryDict) 47 | var encodedQueryStr = "" 48 | if (queryDict){ 49 | var encodedKeyValueStrList = [] 50 | 51 | var keyValueList = Object.entries(queryDict) 52 | console.log("keyValueList=%o", keyValueList) 53 | for (const [ eachKey, eachValue ] of keyValueList){ 54 | console.log("eachKey=%s, eachValue=%s", eachKey, eachValue) 55 | var encodedKey = encodeURIComponent(eachKey) 56 | console.log("encodedKey=%o", encodedKey) 57 | var encodedValue = encodeURIComponent(eachValue) 58 | console.log("encodedValue=%o", encodedValue) 59 | var encodedKeyValueStr = `${encodedKey}=${encodedValue}` 60 | console.log("encodedKeyValueStr=%o", encodedKeyValueStr) 61 | encodedKeyValueStrList.push(encodedKeyValueStr) 62 | } 63 | 64 | console.log("encodedKeyValueStrList=%o", encodedKeyValueStrList) 65 | if (encodedKeyValueStrList.length > 0){ 66 | encodedQueryStr = encodedKeyValueStrList.join("&") 67 | } 68 | } 69 | 70 | console.log("encodedQueryStr=%o", encodedQueryStr) 71 | return encodedQueryStr 72 | } 73 | 74 | 75 | // current url: http://xxx/index.html?q=&tag=Problem%20Solving&difficulty=20 76 | // extract out: q=&tag=Problem%20Solving&difficulty=20 77 | function getCurQueryStr(){ 78 | console.log("getCurQueryStr") 79 | var curQuerySearch = window.location.search.substring(1) 80 | console.log("curQuerySearch=%s", curQuerySearch) 81 | return curQuerySearch 82 | } 83 | 84 | // input: None 85 | // current url is: http://xxx/index.html?q=&tag=Lost%20and%20Found&difficulty=20 86 | // output: {q: "", tag: "Lost and Found", difficulty: "20"} 87 | function getCurQueryDict(){ 88 | console.log("getCurQueryDict") 89 | var decodedQueryDict = decodeQueryStr(getCurQueryStr()) 90 | console.log("decodedQueryDict=%o", decodedQueryDict) 91 | return decodedQueryDict 92 | } 93 | 94 | // function: get query value from current query search string via query key 95 | // input: "book_id" 96 | // current url: http://xxx/book_detail.html?book_id=5bd7bd3ebfaa44fe2c737678 97 | // output: [true, "5bd7bd51bfaa44fe2c737c78"] 98 | // exmaple: 99 | // var getOk, curBookId 100 | // [getOk, curBookId] = getQueryValueFromCurSearch("book_id") 101 | function getQueryValueFromCurSearch(queryKey){ 102 | console.log("getQueryValueFromCurSearch: queryKey=%s", queryKey) 103 | var currentQueryDict = getCurQueryDict() 104 | return getValueFromDict(currentQueryDict, queryKey) 105 | } 106 | 107 | 108 | function redirectToUrl(baseUrl, queryParaDict={}){ 109 | console.log("redirectToUrl: baseUrl=%s, queryParaDict=%o", baseUrl, queryParaDict) 110 | 111 | var encodedQueryStr = "" 112 | if (queryParaDict){ 113 | encodedQueryStr = encodeQueryDict(queryParaDict) 114 | } 115 | console.log("encodedQueryStr=", encodedQueryStr) 116 | 117 | var fullUrl = baseUrl 118 | if (encodedQueryStr) { 119 | fullUrl += "?" + encodedQueryStr 120 | } 121 | // console.log("fullUrl=", fullUrl) 122 | console.log("Now redirect to url: %s", fullUrl) 123 | window.location = fullUrl 124 | } 125 | 126 | // from: file:///xxx/StorybookQueryWeb/index.html?q=&tag=Individuality&difficulty=7 127 | // got: /xxx/StorybookQueryWeb/index.html 128 | function getCurUrlPath(){ 129 | // var curUrl = window.location.href.substring(window.location.href.lastIndexOf('/') + 1).split("?")[0] 130 | // console.log("curUrl=%o", curUrl) 131 | // locationHref="file:///xxx/StorybookQueryWeb/index.html?q=&tag=Individuality&difficulty=7" 132 | var locationPathname = window.location.pathname 133 | console.log("locationPathname=%o", locationPathname) 134 | return locationPathname 135 | } 136 | 137 | 138 | // clear query string in url: index.html?q=&tag=Individuality&difficulty=7 139 | // to : index.html 140 | function clearQueryStrInUrl(){ 141 | // var locationHref = window.location.href 142 | // console.log("locationHref=%s", locationHref) 143 | var curUrl = getCurUrlPath() 144 | console.log("curUrl=%s", curUrl) 145 | var curTitle = document.title 146 | console.log("curTitle=%s", curTitle) 147 | // window.history.pushState({"q": ""}, curTitle, curUrl) 148 | window.history.pushState({}, curTitle, curUrl) 149 | } 150 | -------------------------------------------------------------------------------- /php/crifanLib.php: -------------------------------------------------------------------------------- 1 | "); 32 | } 33 | 34 | 35 | //http://php.net/manual/en/book.curl.php 36 | function http_response($url, $status = null, $wait = 3) 37 | { 38 | $time = microtime(true); 39 | $expire = $time + $wait; 40 | 41 | // we fork the process so we don't have to wait for a timeout 42 | $pid = pcntl_fork(); 43 | if ($pid == -1) { 44 | die('could not fork'); 45 | } else if ($pid) { 46 | // we are the parent 47 | $ch = curl_init(); 48 | curl_setopt($ch, CURLOPT_URL, $url); 49 | 50 | curl_setopt($ch, CURLOPT_HEADER, TRUE); 51 | //curl_setopt($ch, CURLOPT_NOBODY, TRUE); // remove body 52 | //curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); 53 | $head = curl_exec($ch); 54 | $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); 55 | curl_close($ch); 56 | 57 | if(!$head) 58 | { 59 | return FALSE; 60 | } 61 | 62 | if($status === null) 63 | { 64 | if($httpCode < 400) 65 | { 66 | return TRUE; 67 | } 68 | else 69 | { 70 | return FALSE; 71 | } 72 | } 73 | elseif($status == $httpCode) 74 | { 75 | return TRUE; 76 | } 77 | 78 | return FALSE; 79 | pcntl_wait($status); //Protect against Zombie children 80 | } else { 81 | // we are the child 82 | while(microtime(true) < $expire) 83 | { 84 | sleep(0.5); 85 | } 86 | return FALSE; 87 | } 88 | } 89 | 90 | //http://cn2.php.net/manual/zh/function.fsockopen.php 91 | function getUrlRespHtml_fsockopen($url) { 92 | printAutoNewline("input url=".$url); 93 | 94 | $respHtml = ""; 95 | 96 | //resource fsockopen ( string $hostname [, int $port = -1 [, int &$errno [, string &$errstr [, float $timeout = ini_get("default_socket_timeout") ]]]] ) 97 | // $testHostname = "www.yell.com"; 98 | // $fp = fsockopen($testHostname, 80, $errno, $errstr, 30); 99 | // if (!$fp) { 100 | // echo "$errstr ($errno)
\n"; 101 | // } else { 102 | // $getRequest = "GET / HTTP/1.1\r\n"; 103 | // $getRequest .= "Host: ".$testHostname."\r\n"; 104 | // $getRequest .= "Connection: Close\r\n\r\n"; 105 | // fwrite($fp, $getRequest); 106 | // while (!feof($fp)) { 107 | // $curRespHtml = fgets($fp, 128); 108 | // printAutoNewline($curRespHtml); 109 | // $respHtml += $curRespHtml; 110 | // } 111 | // fclose($fp); 112 | // } 113 | //printAutoNewline($respHtml); 114 | 115 | http_response($url,'200',3); // returns true if the response takes less than 3 seconds and the response code is 200 116 | 117 | return $respHtml; 118 | } 119 | 120 | /* 121 | *【已解决】PHP中把字符串文本等数据保存到文件中 122 | * http://www.crifan.com/php_how_to_save_string_text_data_into_file/ 123 | */ 124 | function saveToFile($dataToOutput, $fullFilename) 125 | { 126 | // $outputFp = fopen($outputFilename, 'w') or die("can't create file".$outputFilename); 127 | // fwrite($outputFp, $response); 128 | // fclose($outputFp); 129 | 130 | $saveResult = file_put_contents($fullFilename, $dataToOutput); 131 | printAutoNewline("Write data to ".$fullFilename." ok."); 132 | 133 | return $saveResult; 134 | } 135 | 136 | /* 137 | * 【已解决】PHP中如何实现路径拼接(两个路径合并)以及合并文件夹路径和文件名 138 | * http://www.crifan.com/php_path_concatenation_combine_directory_and_filename 139 | * eg: 140 | * from: 141 | * D:\tmp\WordPress\DevRoot\httpd-2.2.19-win64\httpd-2.2-x64\htdocs\php_test\35934503_data 142 | * cookie_jar.txt 143 | * to: 144 | * D:\tmp\WordPress\DevRoot\httpd-2.2.19-win64\httpd-2.2-x64\htdocs\php_test\35934503_data\cookie_jar.txt 145 | */ 146 | function concatenatePath($headPath, $tailPath) 147 | { 148 | $realHeadPath = realpath($headPath); 149 | #printAutoNewline("realHeadPath=".$realHeadPath); 150 | //$realTailPath = realpath($tailPath); 151 | //printAutoNewline("realTailPath=".$realTailPath); 152 | //$concatnatedPath = $realHeadPath.DIRECTORY_SEPARATOR.$realTailPath; 153 | #printAutoNewline("tailPath=".$tailPath); 154 | 155 | $concatnatedPath = $realHeadPath.DIRECTORY_SEPARATOR.$tailPath; 156 | #printAutoNewline("concatnatedPath=".$concatnatedPath); 157 | return $concatnatedPath; 158 | } 159 | 160 | //http://cn2.php.net/curl_setopt 161 | function getUrlRespHtml($url) 162 | { 163 | printAutoNewline("now to get response from url=".$url); 164 | 165 | //get the file (e.g. image) and output it to the browser 166 | $ch = curl_init(); //open curl handle 167 | curl_setopt($ch, CURLOPT_URL, $url); //set an url 168 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //do not output directly, use variable 169 | curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1); //do a binary transfer 170 | curl_setopt($ch, CURLOPT_FAILONERROR, 1); //stop if an error occurred 171 | 172 | curl_setopt($ch, CURLOPT_AUTOREFERER, 1); //当根据Location:重定向时,自动设置header中的Referer:信息。 173 | 174 | #printAutoNewline("now add CURLOPT_COOKIEJAR support"); 175 | $cookieJarFilename = "cookie_jar.txt"; 176 | //$cookieJarFullname = dirname(__FILE__).$cookieJarFilename; 177 | $cookieJarFullname = concatenatePath(dirname(__FILE__), $cookieJarFilename); 178 | printAutoNewline("cookieJarFullname=".$cookieJarFullname); 179 | curl_setopt($ch, CURLOPT_COOKIEJAR, $cookieJarFullname); 180 | curl_setopt($ch, CURLOPT_COOKIEFILE, $cookieJarFullname); 181 | 182 | curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)"); 183 | 184 | $requestHeader[0] = "Accept: text/html, application/xhtml+xml, */*,"; 185 | //$requestHeader[] = "Cache-Control: max-age=0"; 186 | $requestHeader[] = "Connection: keep-alive"; 187 | //$requestHeader[] = "Keep-Alive: 300"; 188 | //$requestHeader[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7"; 189 | //$requestHeader[] = "Accept-Language: en-us,en;q=0.5"; 190 | //$requestHeader[] = "Pragma: "; // browsers keep this blank. 191 | curl_setopt($ch, CURLOPT_HTTPHEADER, $requestHeader); 192 | 193 | $response = curl_exec($ch); //store the content in variable 194 | 195 | if(!curl_errno($ch)) 196 | { 197 | //send out headers and output 198 | header("Content-type: ".curl_getinfo($ch, CURLINFO_CONTENT_TYPE).""); 199 | header("Content-Length: ".curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD).""); 200 | 201 | //printAutoNewline($response); 202 | } 203 | else{ 204 | printAutoNewline('Curl error: ' . curl_error($ch)); 205 | } 206 | curl_close($ch); //close curl handle 207 | 208 | return $response; 209 | } 210 | 211 | printAutoNewline("DIRECTORY_SEPARATOR=".DIRECTORY_SEPARATOR); 212 | 213 | $yellEntryUrl = "http://www.yell.com/"; 214 | $yesllRespHtml = getUrlRespHtml($yellEntryUrl); 215 | //printAutoNewline("yesllRespHtml=".$yesllRespHtml); 216 | $outputFilename = "respHtml.html"; 217 | saveToFile($yesllRespHtml, $outputFilename); 218 | 219 | 220 | $keywords = "plumbers"; 221 | $yellSearchUrl = "http://www.yell.com/ucs/UcsSearchAction.do?keywords=".$keywords."&location=&scrambleSeed=30792452&searchType=&M=&bandedclarifyResults=&ssm=1"; 222 | $searchRespHtml = getUrlRespHtml($yellSearchUrl); 223 | saveToFile($yesllRespHtml, "searchResult.html"); 224 | 225 | // $outestDivPattern = '#
(.+)
#is'; 226 | 227 | // preg_match($outestDivPattern, $testStr, $matches); 228 | // print_r($matches); #注意,通过网页方式查看打印出来的字符,是看不到div的,需要查看网页源代码,就可以看出来对应的div了 229 | 230 | ?> -------------------------------------------------------------------------------- /python/README.md: -------------------------------------------------------------------------------- 1 | 20190109 has moved to new seperated git repository: [crifanLibPython](https://github.com/crifan/crifanLibPython) 2 | -------------------------------------------------------------------------------- /python/crifanLib/README.md: -------------------------------------------------------------------------------- 1 | 20190109 has moved to new seperated git repository: 2 | 3 | https://github.com/crifan/crifanLibPython/blob/master/python2/crifanLib.py 4 | -------------------------------------------------------------------------------- /swift/CrifanCGSize.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CrifanCGSize.swift 3 | // Crifan Li 4 | // Updated: 2017/09/28 5 | // 6 | 7 | import UIKit 8 | 9 | extension CGSize { 10 | 11 | func scaleToSize(_ maxSize:CGSize) -> CGSize { 12 | print("self=\(self), maxSize=\(maxSize)") 13 | 14 | var newSize = self 15 | 16 | let maxWidth:CGFloat = maxSize.width 17 | let maxHeight:CGFloat = maxSize.height 18 | 19 | let widthRatio:CGFloat = self.width / maxWidth 20 | let heightRatio:CGFloat = self.height / maxHeight 21 | print("widthRatio=\(widthRatio), heightRatio=\(heightRatio)") 22 | //widthRatio=8.33333, heightRatio=3.70741 23 | 24 | if (self.width >= maxWidth) && (self.height >= maxHeight) { 25 | if widthRatio > heightRatio { 26 | newSize.width = self.width / widthRatio 27 | newSize.height = self.height / widthRatio 28 | } else { 29 | newSize.width = self.width / heightRatio 30 | newSize.height = self.height / heightRatio 31 | } 32 | } else if (self.width < maxWidth) && (self.height >= maxHeight) { 33 | newSize.width = self.width / heightRatio 34 | newSize.height = self.height / heightRatio 35 | } else if (self.width >= maxWidth) && (self.height < maxHeight) { 36 | newSize.width = self.width / widthRatio 37 | newSize.height = self.height / widthRatio 38 | } else { 39 | newSize.width = self.width 40 | newSize.height = self.height 41 | } 42 | 43 | print("self=\(self), maxSize=\(maxSize) scale to newSize=\(newSize)") 44 | //self=(3000.0, 2002.0), maxSize=(360.0, 540.0) scale to newSize=(360.0, 240.24) 45 | 46 | return newSize 47 | } 48 | 49 | } 50 | 51 | -------------------------------------------------------------------------------- /swift/CrifanCollection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CrifanCollection.swift 3 | // Crifan Li 4 | // Updated: 2017/09/28 5 | // 6 | 7 | import Foundation 8 | 9 | extension Collection { 10 | var notEmpty: Bool { 11 | return !self.isEmpty 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /swift/CrifanInt.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CrifanInt.swift 3 | // Crifan Li 4 | // Updated: 2017/09/28 5 | // 6 | 7 | import UIKit 8 | 9 | extension Int { 10 | @nonobjc static let InvalidIndex:Int = -1 11 | 12 | //when some value is index, should be >=0, should not < 0 13 | var isValidIndex: Bool { 14 | return (self >= 0) 15 | } 16 | 17 | @nonobjc static let InvalidId:Int = 0 18 | 19 | //when some value is id -> means can not be 0, should > 0 20 | var isValidId: Bool { 21 | return (self > 0) 22 | } 23 | } 24 | 25 | @discardableResult 26 | func genSizeStr(_ sizeInBytes:Int64, unitSize:Int64 = 0, sizeFormat:String = "") -> String { 27 | let sizeB:Int64 = 1 28 | let sizeKB:Int64 = 1024 29 | let sizeMB:Int64 = sizeKB * 1024 30 | let sizeGB:Int64 = sizeMB * 1024 31 | let sizeTB:Int64 = sizeGB * 1024 32 | let sizePB:Int64 = sizeTB * 1024 33 | 34 | // print("\(Int16.max)") //32767 35 | // print("\(Int32.max)") //2147483647 36 | // print("\(Int64.max)") //9223372036854775807 37 | // print("\(Int.max)") //9223372036854775807 38 | 39 | var sizeStr = "" 40 | var curUnitSize = unitSize 41 | var curSizeFormat = sizeFormat 42 | var suffixStr = "" 43 | 44 | //print("curUnitSize=\(curUnitSize), curSizeFormat=\(curSizeFormat)") 45 | 46 | if sizeInBytes < sizeKB { 47 | if curUnitSize == 0 { 48 | curUnitSize = sizeB 49 | } 50 | 51 | if curSizeFormat.isEmpty { 52 | curSizeFormat = "%.0f" 53 | } 54 | 55 | suffixStr = " B" 56 | } else if (sizeInBytes >= sizeKB) && (sizeInBytes < sizeMB) { 57 | if curUnitSize == 0 { 58 | curUnitSize = sizeKB 59 | } 60 | 61 | if curSizeFormat.isEmpty { 62 | curSizeFormat = "%.2f" 63 | } 64 | 65 | suffixStr = " KB" 66 | } else if (sizeInBytes >= sizeMB) && (sizeInBytes < sizeGB) { 67 | if curUnitSize == 0 { 68 | curUnitSize = sizeMB 69 | } 70 | 71 | if curSizeFormat.isEmpty { 72 | curSizeFormat = "%.2f" 73 | } 74 | 75 | suffixStr = " MB" 76 | } else if (sizeInBytes >= sizeGB) && (sizeInBytes < sizeTB) { 77 | if curUnitSize == 0 { 78 | curUnitSize = sizeGB 79 | } 80 | 81 | if curSizeFormat.isEmpty { 82 | curSizeFormat = "%.2f" 83 | } 84 | 85 | suffixStr = " GB" 86 | } else if (sizeInBytes >= sizeTB) && (sizeInBytes < sizePB) { 87 | if curUnitSize == 0 { 88 | curUnitSize = sizeTB 89 | } 90 | 91 | if curSizeFormat.isEmpty { 92 | curSizeFormat = "%.2f" 93 | } 94 | 95 | suffixStr = " TB" 96 | } else if sizeInBytes >= sizePB { 97 | if curUnitSize == 0 { 98 | curUnitSize = sizePB 99 | } 100 | 101 | if curSizeFormat.isEmpty { 102 | curSizeFormat = "%.2f" 103 | } 104 | 105 | suffixStr = " PB" 106 | } 107 | 108 | print("curUnitSize=\(curUnitSize), curSizeFormat=\(curSizeFormat)") 109 | 110 | let sizeFloat:Float = Float(sizeInBytes) / Float(curUnitSize) 111 | //print("sizeFloat=\(sizeFloat)") 112 | 113 | let sizeFloatStr = String(format: curSizeFormat, sizeFloat) 114 | //print("sizeFloatStr=\(sizeFloatStr)") 115 | 116 | sizeStr = sizeFloatStr + suffixStr 117 | 118 | print("sizeInBytes=\(sizeInBytes) -> sizeStr=\(sizeStr)") 119 | 120 | return sizeStr 121 | } 122 | 123 | 124 | /*************************************************************************** 125 | * Number Related functions 126 | ***************************************************************************/ 127 | 128 | //get random int number within range: lower<=random<=upper 129 | func getRandomInRange(_ lower:Int, upper:Int) -> Int { 130 | return lower + Int(arc4random_uniform(UInt32(upper - lower + 1))) 131 | } 132 | 133 | //get unique random int array number within range: lower<=random<=upper 134 | func getUniqueRandomArrayInRange(_ lower:Int, upper:Int, arrCount:Int) -> [Int] { 135 | //print("lower=\(lower), upper=\(upper), arrCount=\(arrCount)") 136 | 137 | let singleRoundNum:Int = upper - lower + 1 138 | //print("singleRoundNum=\(singleRoundNum)") 139 | 140 | let invalidRandomNum = upper + 1 141 | //print("invalidRandomNum=\(invalidRandomNum)") 142 | //var uniqueRandomArr:[Int] = [Int](count: arrCount, repeatedValue: invalidRandomNum) 143 | var uniqueRandomArr:[Int] = [Int]() 144 | //print("uniqueRandomArr=\(uniqueRandomArr)") 145 | 146 | let remain = arrCount % singleRoundNum 147 | //print("remain=\(remain)") 148 | var maxRoundNum = arrCount / singleRoundNum 149 | if remain > 0 { 150 | maxRoundNum += 1 151 | } 152 | //print("maxRoundNum=\(maxRoundNum)") 153 | let maxRoundIdx = maxRoundNum - 1 154 | //print("maxRoundIdx=\(maxRoundIdx)") 155 | 156 | for roundIdx in 0...maxRoundIdx { 157 | //print("roundIdx=\(roundIdx)") 158 | 159 | var curRoundMaxNum:Int = 0 160 | if roundIdx < maxRoundIdx { 161 | curRoundMaxNum = singleRoundNum 162 | } 163 | else if roundIdx == maxRoundIdx { 164 | curRoundMaxNum = arrCount - roundIdx * singleRoundNum 165 | } 166 | //print("curRoundMaxNum=\(curRoundMaxNum)") 167 | 168 | let curRoundMaxIdx = curRoundMaxNum - 1 169 | //print("curRoundMaxIdx=\(curRoundMaxIdx)") 170 | 171 | var curRoundUniqueRandomArr:[Int] = [Int](repeating: invalidRandomNum, count: curRoundMaxNum) 172 | //print("curRoundUniqueRandomArr=\(curRoundUniqueRandomArr)") 173 | 174 | for idxWithinRound in 0...curRoundMaxIdx { 175 | //print("idxWithinRound=\(idxWithinRound)") 176 | var curRandomNum:Int 177 | 178 | repeat { 179 | curRandomNum = getRandomInRange(lower, upper: upper) 180 | //print("curRandomNum=\(curRandomNum)") 181 | }while(curRoundUniqueRandomArr.contains(curRandomNum)) 182 | 183 | curRoundUniqueRandomArr[idxWithinRound] = curRandomNum 184 | //print("[\(idxWithinRound)] curRoundUniqueRandomArr=\(curRoundUniqueRandomArr)") 185 | } 186 | 187 | uniqueRandomArr += curRoundUniqueRandomArr 188 | //uniqueRandomArr.appendContentsOf(curRoundUniqueRandomArr) 189 | //print("uniqueRandomArr=\(uniqueRandomArr)") 190 | } 191 | 192 | return uniqueRandomArr 193 | } 194 | -------------------------------------------------------------------------------- /swift/CrifanLib/CrifanLib.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CrifanLib.swift 3 | // Crifan Li 4 | // Updated: 2017/09/28 5 | // 6 | 7 | import UIKit 8 | //import Foundation 9 | 10 | /*************************************************************************** 11 | * NSUserDefaults functions 12 | ***************************************************************************/ 13 | 14 | func isFirstRunApp() -> Bool { 15 | var isFirstRun = false 16 | 17 | let keyHasLaunchedBefore = "hasLaunchedBefore" 18 | 19 | let hasLaunchedBefore = NSUserDefaults.standardUserDefaults().boolForKey(keyHasLaunchedBefore) 20 | if hasLaunchedBefore { 21 | isFirstRun = false 22 | } 23 | else { 24 | isFirstRun = true 25 | NSUserDefaults.standardUserDefaults().setBool(true, forKey: keyHasLaunchedBefore) 26 | } 27 | 28 | return isFirstRun 29 | } 30 | 31 | let kLastTimeIsLogined = "lastTimeIsLogined" 32 | 33 | //last time is logined 34 | func lastIsLogined() -> Bool { 35 | return NSUserDefaults.standardUserDefaults().boolForKey(kLastTimeIsLogined) 36 | } 37 | 38 | //record current time logined 39 | func saveLastLogined(hasLogined:Bool) { 40 | NSUserDefaults.standardUserDefaults().setBool(hasLogined, forKey: kLastTimeIsLogined) 41 | } 42 | 43 | /*************************************************************************** 44 | * Calculation functions 45 | ***************************************************************************/ 46 | 47 | func generateBase64Str(inputStr:String) -> String { 48 | print("inputStr=\(inputStr)") //user-4e21fc48-179d-4db9-a61b-eb2e7e2e033a:111111 49 | let inputData:NSData = inputStr.dataUsingEncoding(NSUTF8StringEncoding)! 50 | // let inputData = inputStr.dataUsingEncoding(NSASCIIStringEncoding) 51 | print("inputData=\(inputData)") 52 | //Optional(<75736572 2d346532 31666334 382d3137 39642d34 6462392d 61363162 2d656232 65376532 65303333 613a3131 31313131>) 53 | let base64Str:String = inputData.base64EncodedStringWithOptions([]) 54 | //let base64Str:String = (inputData?.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0)))! 55 | // let base64Str:String = (inputData?.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength))! 56 | print("base64Str=\(base64Str)") 57 | //dXNlci00ZTIxZmM0OC0xNzlkLTRkYjktYTYxYi1lYjJlN2UyZTAzM2E6MTExMTEx 58 | return base64Str 59 | } 60 | 61 | /*************************************************************************** 62 | * Date Time Related functions 63 | ***************************************************************************/ 64 | 65 | 66 | class CalculateElapsedTime: NSObject { 67 | static var gCalcTime:[String:CalculateElapsedTime] = [String:CalculateElapsedTime]() 68 | static var gCalcTimeSummary:[String:Double] = [String:Double]() 69 | 70 | let uniqueId:String 71 | 72 | var startTime:NSDate 73 | var endTime:NSDate 74 | 75 | var elapsedTimeInSec:Double { 76 | let diffTimeInSec:Double = self.endTime.timeIntervalSinceDate(self.startTime) 77 | return diffTimeInSec 78 | } 79 | 80 | init(uniqueId:String = ""){ 81 | self.uniqueId = uniqueId 82 | 83 | self.startTime = NSDate() 84 | self.endTime = self.startTime 85 | } 86 | 87 | func start() -> NSDate { 88 | self.reset() 89 | 90 | return self.startTime 91 | } 92 | 93 | func stop() -> Double { 94 | self.endTime = NSDate() 95 | 96 | return elapsedTimeInSec 97 | } 98 | 99 | func reset(){ 100 | self.startTime = NSDate() 101 | self.endTime = self.startTime 102 | } 103 | } 104 | 105 | func calcTimeStart(uniqueId:String){ 106 | print("\(uniqueId)") 107 | 108 | let calcTime = CalculateElapsedTime() 109 | CalculateElapsedTime.gCalcTime[uniqueId] = calcTime 110 | } 111 | 112 | func calcTimeEnd(uniqueId:String){ 113 | if CalculateElapsedTime.gCalcTime.keys.contains(uniqueId) { 114 | let calcTime = CalculateElapsedTime.gCalcTime[uniqueId]! 115 | let elapsedTimeDouble = calcTime.stop() 116 | let elapsedTimeStr = String(format: "%.4f", elapsedTimeDouble) 117 | print("\(uniqueId) elapsedTime=\(elapsedTimeStr)") 118 | calcTime.reset() 119 | 120 | //save calculated time 121 | CalculateElapsedTime.gCalcTimeSummary[uniqueId] = elapsedTimeDouble 122 | } 123 | } 124 | 125 | 126 | /*************************************************************************** 127 | * Phone functions 128 | ***************************************************************************/ 129 | 130 | func doPhoneCall(_ curPhone:String) -> Bool { 131 | var callOk = false 132 | 133 | if curPhone.notEmpty { 134 | let phoneTelStr = "tel://" + curPhone 135 | gLog.verbose("phoneTelStr=\(phoneTelStr)") 136 | //phoneTelStr=tel://13800001111 137 | if let phoneTelNsurl = URL(string: phoneTelStr) { 138 | gLog.verbose("phoneTelNsurl=\(phoneTelNsurl)") 139 | //phoneTelNsurl=tel://13800001111 140 | 141 | let application:UIApplication = UIApplication.shared 142 | if application.canOpenURL(phoneTelNsurl) { 143 | callOk = true 144 | 145 | UIApplication.shared.openURL(phoneTelNsurl) 146 | } else { 147 | //Note: 148 | //in iOS Simulator will fail: 149 | //canOpenURL: failed for URL: "tel://13800001111" - error: "This app is not allowed to query for scheme tel" 150 | print("application can not open: \(phoneTelNsurl)") 151 | } 152 | } 153 | } else { 154 | print("can not call for empty phone") 155 | } 156 | 157 | return callOk 158 | } -------------------------------------------------------------------------------- /swift/Device/CrifanDevice.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CrifanDevice.swift 3 | // Crifan Li 4 | // Updated: 2017/11/09 5 | // 6 | 7 | import UIKit 8 | 9 | extension UIDevice { 10 | static func rotateTo(newDirection:UIInterfaceOrientation) { 11 | self.current.setValue(newDirection.rawValue, forKey: "orientation") 12 | } 13 | 14 | static func rotateToIfNeed(newDirection:UIInterfaceOrientation) { 15 | if !self.isOrientation(toCmpOrientation: newDirection) { 16 | self.rotateTo(newDirection: newDirection) 17 | } 18 | } 19 | 20 | static func isOrientation(toCmpOrientation:UIInterfaceOrientation) -> Bool { 21 | //Note: 22 | // self.current.orientation is UIDeviceOrientation 23 | // toCmpOrientation is UIInterfaceOrientation 24 | // but first 5 type: unknown/portrait/portraitUpsideDown/landscapeLeft/landscapeRight 25 | // of enum value is equal 26 | return self.current.orientation.rawValue == toCmpOrientation.rawValue 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /swift/Http/CrifanLibHttpDemo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CrifanLibHttpDemo.swift 3 | // Xxx 4 | // 5 | // Created by licrifan on 16/6/7. 6 | // Copyright © 2016年 licrifan. All rights reserved. 7 | // 8 | // Last Update: 2016-06-07 9 | 10 | import UIKit 11 | import Alamofire 12 | import SwiftyJSON 13 | 14 | //unuseful function CrifanLibHttpDemo, just for demo usage for CrifanLibHttp 15 | func CrifanLibHttpDemo(){ 16 | 17 | //demo get 18 | func getUserId() { 19 | gLog.info("") 20 | 21 | //let curPhone:String = self.loginView.phoneTextField.text! 22 | let curPhone:String = "13800000000" 23 | gLog.debug("curPhone=\(curPhone)") 24 | 25 | getUrlRespDataJson_async( 26 | .GET, 27 | url: ServerApi.getUserIdUrl(curPhone), 28 | respJsonHandler: getUserIdHandler) 29 | } 30 | 31 | func getUserIdHandler(respDataJson:Alamofire.Result, mergedAllPara:Dictionary) { 32 | gLog.verbose("respDataJson.debugDescription=\(respDataJson.debugDescription)") 33 | 34 | switch respDataJson { 35 | case .Success(let dataJson): 36 | gLog.verbose("dataJson=\(dataJson)") 37 | 38 | gCurUserItem.id = dataJson.int! 39 | 40 | gLog.info("成功获取用ID:\(gCurUserItem.id)") 41 | //成功获取用ID:10000010 42 | 43 | gCurUserItem.phone = "13800000000" 44 | 45 | gCurUserItem.password = "123456" 46 | 47 | //getAcessToken() 48 | case .Failure(let error): 49 | gLog.verbose("error=\(error)") 50 | 51 | let fullErrStr = genFullErrorStr("获取用户ID失败", error: error) 52 | gLog.verbose("fullErrStr=\(fullErrStr)") 53 | //self.noticeError(fullErrStr, autoClear: true) 54 | } 55 | } 56 | 57 | //demo Post with parameters 58 | 59 | func getSmsCodeHandler(respDataJson:Alamofire.Result, mergedAllPara:Dictionary) { 60 | gLog.verbose("respDataJson.debugDescription=\(respDataJson.debugDescription)") 61 | //respDataJson.debugDescription=SUCCESS: 13812345678 62 | //respDataJson.debugDescription=FAILURE: Error Domain=HttpErrorDomain Code=403 "(null)" UserInfo={message=The phone is not found , code=403} 63 | 64 | switch respDataJson { 65 | case .Success(let dataJson): 66 | gLog.verbose("dataJson=\(dataJson)") 67 | //dataJson=13812345678 68 | 69 | //self.noticeSuccess("已成功发送短信验证码,请注意查收", autoClear: true) 70 | case .Failure(let error): 71 | gLog.verbose("error=\(error)") 72 | //error=Error Domain=HttpErrorDomain Code=403 "(null)" UserInfo={message=The phone is not found , code=403} 73 | 74 | let errMsg = getHttpRespErrMsg(error) 75 | gLog.verbose("errMsg=\(errMsg)") 76 | 77 | //self.noticeError(errMsg, autoClear: true) 78 | } 79 | } 80 | 81 | //if validate4SShopCode() && validatePhonenumber(self.registerView.phoneTextField.text!) { 82 | getUrlRespDataJson_async( 83 | .POST, 84 | url: ServerApi.getSmsCodeUrl(), 85 | parameters: [ 86 | //"phone" : self.registerView.phoneTextField.text!, 87 | "codetype" : "register", 88 | //"dealer" : self.registerView.shop4SCodeTextField.text!, 89 | ], 90 | respJsonHandler: getSmsCodeHandler) 91 | //} 92 | 93 | 94 | //demo get with extarPara 95 | func getCustomerDetailInfo(customerDetailVC:CustomerDetailViewController) { 96 | gLog.verbose("customerDetailVC=\(customerDetailVC)") 97 | 98 | getUrlRespDataJson_async( 99 | .GET, 100 | url: ServerApi.getCustomerDetailUrl(gCurUserItem.id, customerId: customerDetailVC.curCustomerItem.id), 101 | extraPara: ["customerDetailVC" : customerDetailVC], 102 | respJsonHandler: getCustomerDetailInfoHandler) 103 | } 104 | 105 | func getCustomerDetailInfoHandler(respDataJson:Alamofire.Result, mergedAllPara:Dictionary) { 106 | gLog.verbose("respDataJson.debugDescription=\(respDataJson.debugDescription)") 107 | 108 | let customerDetailVC = mergedAllPara["extraPara"]!["customerDetailVC"]! as! CustomerDetailViewController 109 | 110 | switch respDataJson { 111 | case .Success(let dataJson): 112 | gLog.verbose("dataJson=\(dataJson)") 113 | 114 | parseJsonToCustomerItem(dataJson, curCustomerItem: customerDetailVC.curCustomerItem) 115 | 116 | customerDetailVC.curCustomerItem.gotAllInfo = true 117 | 118 | customerDetailVC.updateCustomerInfo() 119 | 120 | case .Failure(let error): 121 | gLog.error("error=\(error) for get customer detail info for \(customerDetailVC.curCustomerItem)") 122 | } 123 | } 124 | 125 | } -------------------------------------------------------------------------------- /swift/String/CrifanStringDemo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CrifanStringDemo.swift 3 | // Xxx 4 | // 5 | // Created by licrifan on 16/7/16. 6 | // Copyright © 2016年 licrifan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /* 12 | if realBadgeStr.notEmpty { 13 | 14 | } 15 | 16 | 17 | let addMoreTitleList = ["Create Group".localized, "Create Topic".localized] 18 | 19 | 20 | var familyNameChar:String = newContactNameStr.firstChar 21 | 22 | 23 | return inputStr.replace("\\\"", to: "\"") 24 | 25 | 26 | func genHeaderText(headerName:String) -> String { 27 | // print("\r\ngenHeaderText") 28 | var headerText = headerName 29 | 30 | if headerName.isEmpty { 31 | return headerText 32 | } 33 | 34 | if headerName.characters.count <= 1 { 35 | return headerText 36 | } 37 | 38 | //default use first two char 39 | let firstTwoChar = (headerName as NSString).substringToIndex(2) 40 | headerText = firstTwoChar.uppercaseString 41 | 42 | //if all chinese name, get last one cn char 43 | if headerName.isAllChineseCharacters { 44 | headerText = headerName.lastChar 45 | }else if headerName.isAllLetters { 46 | let firstTwoChar = (headerName as NSString).substringToIndex(2) 47 | headerText = firstTwoChar.uppercaseString 48 | }else if headerName.isAllDigits { 49 | headerText = (headerName as NSString).substringToIndex(2) 50 | }else if headerName.containsString(" "){ 51 | let removedSpaceName = headerName.replace(" ", to: "") 52 | 53 | if removedSpaceName.isAllLetters { 54 | let subStrArr:[String] = headerName.splitToStrArr() 55 | 56 | let firstStr = subStrArr[0] 57 | let firstChar = getFirstChar(firstStr) 58 | 59 | var mergedTwoChar = firstChar 60 | 61 | if subStrArr.count >= 2 { 62 | let secondStr = subStrArr[1] 63 | if !secondStr.isEmpty { 64 | let sencondChar = getFirstChar(secondStr) 65 | mergedTwoChar += sencondChar 66 | } 67 | } 68 | 69 | headerText = mergedTwoChar.uppercaseString 70 | } 71 | } 72 | 73 | gLog.debug("\(headerName) -> \(headerText)") 74 | return headerText 75 | } 76 | 77 | let trimmedMsgStr = messageStrToSend.trim() 78 | 79 | func filterUnsupportChar(originStr:String) ->String { 80 | var filtedStr = originStr 81 | 82 | //filtedStr = filtedStr.decodedHtml 83 | filtedStr = filtedStr.decodedHtmlEntities 84 | 85 | //iOS UITextView not support 200D, so remove it here 86 | filtedStr = filtedStr.replace("\u{200d}", to: "") 87 | // print("removed 200d: filtedStr=\(filtedStr)") 88 | //seems iOS support 00A0, so no need to replace it here 89 | // filtedStr = filtedStr.replace("\u{00a0}", to: " ") 90 | // print("replaced 00a0: filtedStr=\(filtedStr)") 91 | 92 | return filtedStr 93 | } 94 | 95 | 96 | let singleStr:String = "张三" 97 | let firstCharStr:String = singleStr.firstChar 98 | print("firstCharStr=\(firstCharStr)") //张 99 | 100 | /* 101 | * getLastChar demo 102 | */ 103 | let singleStr2:String = "张三" 104 | let lastCharStr:String = singleStr2.lastChar 105 | print("lastCharStr=\(lastCharStr)") //三 106 | 107 | /* 108 | * splitToCharArr demo 109 | */ 110 | let stringToSplit:String = "一个字符串" 111 | let spiltedCharArr:[Character] = stringToSplitsplitToCharArr.() 112 | print("spiltedCharArr=\(spiltedCharArr)") //["一", "个", "字", "符", "串"] 113 | 114 | /* 115 | * splitToStrArr demo 116 | */ 117 | //let noneSeperatorStr:String = "" 118 | let strWithoutSpace:String = "中间没有空格单个字符串" 119 | //let splitedStrArr_withoutSpace:[String] = strWithoutSpace.splitToStrArr(noneSeperatorStr) 120 | let splitedStrArr_withoutSpace:[String] = strWithoutSpace.splitToStrArr() 121 | print("splitedStrArr_withoutSpace=\(splitedStrArr_withoutSpace)") //["中", "间", "没", "有", "空", "格", "单", "个", "字", "符", "串"] 122 | 123 | let spaceSeperatorStr:String = " " 124 | let stringContainingSpace:String = "中 间 有 空 格 的 字 符 串 " 125 | let splitedStrArr_containingSpace:[String] = stringContainingSpace.splitToStrArr(spaceSeperatorStr) 126 | print("splitedStrArr_containingSpace=\(splitedStrArr_containingSpace)") //["中", "间", "有", "空", "格", "的", "字", "符", "串"] 127 | 128 | 129 | /* 130 | * mergeCharArrToSingleStr demo 131 | */ 132 | let charArrToMerge:[Character] = ["待", "合", "并", "字", "符", "数", "组"] 133 | let mergedStr:String = mergeCharArrToSingleStr(charArrToMerge) 134 | print("mergedStr=\(mergedStr)") //"待合并字符数组" 135 | 136 | /* 137 | * mergeStrArrToSingleStr demo 138 | */ 139 | let stringArrToMerge:[String] = ["待", "合", "并", "字", "符", "串","数", "组"] 140 | let mergedSingleStr:String = mergeStrArrToSingleStr(stringArrToMerge) 141 | print("mergedSingleStr=\(mergedSingleStr)") //"待合并字符串数组" 142 | 143 | /* 144 | * translateChineseStrToPinyinWithAccents demo 145 | */ 146 | let chineseStr:String = "昂山素季" 147 | let translatedPinyinStrWithAccents:String = translateChineseStrToPinyinWithAccents(chineseStr) 148 | print("translatedPinyinStrWithAccents=\(translatedPinyinStrWithAccents)") //"áng shān sù jì" 149 | 150 | /* 151 | * removeAccentsFromStr demo 152 | */ 153 | let pinyinWithAccents:String = "áng shān sù jì" 154 | let strippedAccentsPinyin:String = removeAccentsFromStr(pinyinWithAccents) 155 | print("strippedAccentsPinyin=\(strippedAccentsPinyin)") //"ang shan su ji" 156 | 157 | /* 158 | * translateChineseStrToCharPinyinDict demo 159 | */ 160 | let chineseStrToTranslate:String = "昂山素季" 161 | let translatedCharPinyinDict = translateChineseStrToCharPinyinDict(chineseStrToTranslate) 162 | print("translatedCharPinyinDict=\(translatedCharPinyinDict)") //[["昂": "ang"], ["山": "shan"], ["素": "su"], ["季": "ji"]] 163 | 164 | 165 | func testUrlEncodeDecode(){ 166 | let urlWithZhcn = "http://xx.xx.xx.xx/skrDev/src/report/wholesale.html?t=1510295408712&drAreaFiltrateCode=大东南区&drAreaFiltrateName=大东南区" 167 | let urlEncodedPercent = "http://xx.xx.xx.xx/skrDev/src/report/wholesale.html?t=1510295408712&drAreaFiltrateCode=%E5%A4%A7%E4%B8%9C%E5%8D%97%E5%8C%BA&drAreaFiltrateName=%E5%A4%A7%E4%B8%9C%E5%8D%97%E5%8C%BA" 168 | 169 | let encodedZhcnUrl = urlWithZhcn.encodedUrl 170 | //encodedZhcnUrl=http://xx.xx.xx.xx/skrDev/src/report/wholesale.html?t=1510295408712&drAreaFiltrateCode=%E5%A4%A7%E4%B8%9C%E5%8D%97%E5%8C%BA&drAreaFiltrateName=%E5%A4%A7%E4%B8%9C%E5%8D%97%E5%8C%BA 171 | let decoedPercentUrl = urlEncodedPercent.decodedUrl 172 | print("encodedZhcnUrl=\(encodedZhcnUrl)") 173 | print("decoedPercentUrl=\(decoedPercentUrl)") 174 | //decoedPercentUrl=http://xx.xx.xx.xx/skrDev/src/report/wholesale.html?t=1510295408712&drAreaFiltrateCode=大东南区&drAreaFiltrateName=大东南区 175 | } 176 | 177 | */ 178 | -------------------------------------------------------------------------------- /swift/ThirdParty/Charts/CrifanCharts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CrifanCharts.swift 3 | // Crifan Li 4 | // Updated: 2017/09/28 5 | // 6 | 7 | import UIKit 8 | import Charts //https://github.com/danielgindi/Charts 9 | 10 | /*************************************************************************** 11 | * Charts Related functions 12 | * https://github.com/danielgindi/Charts 13 | ***************************************************************************/ 14 | 15 | //do common settings for line/bar chart view 16 | func commonLineBarChartViewSettings(curChartView:BarLineChartViewBase) { 17 | curChartView.noDataText = "暂无数据" 18 | curChartView.noDataTextDescription = "没有找到有效数据" 19 | curChartView.dragEnabled = true 20 | curChartView.setScaleEnabled(true) 21 | curChartView.drawGridBackgroundEnabled = true 22 | curChartView.gridBackgroundColor = UIColor.yellowColor() 23 | curChartView.pinchZoomEnabled = true 24 | curChartView.drawBordersEnabled = true 25 | 26 | if curChartView is BarChartView { 27 | let curBarChatView = curChartView as! BarChartView 28 | curBarChatView.drawBarShadowEnabled = false 29 | curBarChatView.drawHighlightArrowEnabled = true 30 | } 31 | 32 | //curChartView.backgroundColor = UIColor.cyanColor() 33 | //curChartView.backgroundColor = UIColor.brownColor() 34 | curChartView.backgroundColor = UIColor.lightTextColor() 35 | // curChartView.backgroundColor = UIColor.lightGrayColor() 36 | //curChartView.backgroundColor = UIColor.purpleColor() 37 | } 38 | 39 | //set data for single line chart view 40 | func setSingleLineChart(curLineChartView:LineChartView, xPointList: [String], leftYAXisValues: [Double], leftYAxisLabel:String, lineColor:UIColor = UIColor.redColor()) { 41 | var leftYDataEntryList: [ChartDataEntry] = [] 42 | 43 | for i in 0.. Int { 67 | return self.contentList.count 68 | } 69 | 70 | //column 71 | func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 72 | var curItemNum = 0 73 | if section < self.contentList.count { 74 | curItemNum = self.contentList[section].count 75 | } 76 | 77 | gLog.verbose("section=\(section) -> curItemNum=\(curItemNum)") 78 | 79 | return curItemNum 80 | } 81 | 82 | func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { 83 | gLog.verbose("collectionView=\(collectionView)") 84 | 85 | if indexPath.section == 0 { 86 | let horizontalTitleCell = collectionView.dequeueReusableCellWithReuseIdentifier(GridCellId, forIndexPath: indexPath) as! GridCell 87 | 88 | horizontalTitleCell.configCell(ReportCellLabelFont, contentLabelTextColor: CommonTextColorBlack, cellBackgroundColor: ReportLabelHorizontalBackgroundColor) 89 | 90 | var curHorizontalTitle = "" 91 | 92 | //row 0 93 | if indexPath.section < self.contentList.count { 94 | let firstRowTitleList = self.contentList[indexPath.section] 95 | 96 | if indexPath.row < firstRowTitleList.count { 97 | curHorizontalTitle = firstRowTitleList[indexPath.row] 98 | } 99 | } 100 | 101 | gLog.verbose("indexPath=\(indexPath) -> curHorizontalTitle=\(curHorizontalTitle)") 102 | 103 | horizontalTitleCell.contentLabel.text = curHorizontalTitle 104 | 105 | return horizontalTitleCell 106 | } else { 107 | if indexPath.row == 0 { 108 | let verticalTitleCell = collectionView.dequeueReusableCellWithReuseIdentifier(GridCellId, forIndexPath: indexPath) as! GridCell 109 | 110 | verticalTitleCell.configCell(ReportCellLabelFont, contentLabelTextColor: CommonTextColorBlack, cellBackgroundColor: ReportLabelVerticalBackgroundColor) 111 | 112 | var curRowFirstColumnTitle = "" 113 | 114 | //column 0 115 | if indexPath.section < self.contentList.count { 116 | let curRowContentList = self.contentList[indexPath.section] 117 | 118 | if curRowContentList.count > 0 { 119 | curRowFirstColumnTitle = curRowContentList[0] 120 | } 121 | } 122 | 123 | verticalTitleCell.contentLabel.text = curRowFirstColumnTitle 124 | 125 | gLog.verbose("indexPath=\(indexPath) -> curRowFirstColumnTitle=\(curRowFirstColumnTitle)") 126 | 127 | return verticalTitleCell 128 | } else { 129 | let contentCell = collectionView .dequeueReusableCellWithReuseIdentifier(GridCellId, forIndexPath: indexPath) as! GridCell 130 | 131 | contentCell.configCell(GridCellContentLabelFont, contentLabelTextColor: GridCellContentLabelTextColor, cellBackgroundColor: GridCellBackgroundColor) 132 | 133 | var curContentStr = "" 134 | 135 | //for each row 136 | if indexPath.section < self.contentList.count { 137 | let curRowContentList = self.contentList[indexPath.section] 138 | gLog.verbose("curRowContentList=\(curRowContentList)") 139 | 140 | //for each column 141 | if indexPath.row < curRowContentList.count { 142 | curContentStr = curRowContentList[indexPath.row] 143 | } 144 | } 145 | 146 | contentCell.contentLabel.text = curContentStr 147 | 148 | gLog.verbose("indexPath=\(indexPath) -> curContentStr=\(curContentStr)") 149 | 150 | return contentCell 151 | } 152 | } 153 | } 154 | 155 | func updateContentList(newContentList:[[String]]) { 156 | gLog.verbose("newContentList") 157 | 158 | self.contentList = newContentList 159 | 160 | dispatchMain_async({ 161 | if self.contentList.notEmpty { 162 | let firstRowTitleList = self.contentList[0] 163 | self.gridViewLayout.numberOfColumns = firstRowTitleList.count 164 | } 165 | 166 | self.gridViewLayout.prepareLayout() 167 | 168 | self.reloadData() 169 | }) 170 | } 171 | 172 | 173 | } 174 | -------------------------------------------------------------------------------- /swift/ThirdParty/GridView/GridViewDemo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GridViewDemo.swift 3 | // CrifanLibSwift 4 | // 5 | // Created by licrifan on 16/7/3. 6 | // Copyright © 2016年 licrifan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | class GridViewDemo { 13 | /* 14 | import Alamofire 15 | import SwiftyJSON 16 | 17 | class SalesPerfomanceReportViewController: UIViewController { 18 | 19 | let SalesPerfomanceRowTitleList:[String] = [ 20 | "顾问", 21 | "基盘", 22 | "下订", 23 | "成交", 24 | "战败", 25 | "订单率", 26 | "成交率", 27 | ] 28 | 29 | var gridView:GridView 30 | var contentList:[[String]] 31 | 32 | let VisibleColumnNum = 5 33 | let itemHeight:CGFloat = 30 34 | 35 | var salesPerformanceList:[[String]] 36 | 37 | init(){ 38 | self.contentList = [[String]]() 39 | self.gridView = GridView() 40 | 41 | super.init(nibName: nil, bundle: nil) 42 | 43 | self.checkSalesPerfomance() 44 | 45 | self.resetGridview() 46 | } 47 | 48 | required init?(coder aDecoder: NSCoder) { 49 | fatalError("init(coder:) has not been implemented") 50 | } 51 | 52 | /************************************************************************* 53 | * View Controller Functions 54 | *************************************************************************/ 55 | 56 | override func viewDidLoad() { 57 | super.viewDidLoad() 58 | 59 | self.title = "销售业绩报表" 60 | 61 | self.view.backgroundColor = AppBackgoundColor 62 | 63 | self.view.addSubview(self.gridView) 64 | } 65 | 66 | func checkSalesPerfomance(){ 67 | if salesPerformanceList.isEmpty { 68 | 69 | salesPerformanceList = self.initSalesPerfomanceList() 70 | 71 | getUrlRespDataJson_async( 72 | .GET, 73 | url: ServerApi.getSalesPerformanceUrl(gCurUserItem.id), 74 | respJsonHandler: self.getSalesPerfomanceHandler) 75 | } 76 | } 77 | 78 | func initSalesPerfomanceList() -> [[String]] { 79 | var contentList = Array(count: 1, repeatedValue: Array(count: SalesPerfomanceRowTitleList.count, repeatedValue: "")) 80 | 81 | contentList[0] = SalesPerfomanceRowTitleList 82 | 83 | return contentList 84 | } 85 | 86 | func getSalesPerfomanceHandler(respDataJson:Alamofire.Result, mergedAllPara:Dictionary) { 87 | gLog.verbose("respDataJson.debugDescription=\(respDataJson.debugDescription)") 88 | 89 | switch respDataJson { 90 | case .Success(let dataJson): 91 | gLog.verbose("dataJson=\(dataJson)") 92 | 93 | guard let salesPerformanceJsonArr = dataJson.array else { 94 | gLog.error("get empty sales perfomance data") 95 | 96 | return 97 | } 98 | 99 | var curSalesPerformanceList = self.initSalesPerfomanceList() 100 | gLog.verbose("curSalesPerformanceList=\(curSalesPerformanceList)") 101 | //curSalesPerformanceList=[["顾问", "基盘", "下订", "成交", "战败", "订单率", "成交率"]] 102 | 103 | for eachSalesPerformanceJson in salesPerformanceJsonArr { 104 | gLog.verbose("eachSalesPerformanceJson=\(eachSalesPerformanceJson)") 105 | 106 | let curPerformanceItem = parseJsonToSalesPerformanceItem(eachSalesPerformanceJson) 107 | gLog.verbose("curPerformanceItem=\(curPerformanceItem)") 108 | 109 | curSalesPerformanceList.append(curPerformanceItem) 110 | } 111 | 112 | gCurUserItem.salesPerformanceList = curSalesPerformanceList 113 | gLog.verbose("gCurUserItem.salesPerformanceList=\(gCurUserItem.salesPerformanceList)") 114 | 115 | dispatchMain_async({ 116 | self.resetGridview() 117 | self.view.addSubview(self.gridView) 118 | }) 119 | 120 | case .Failure(let error): 121 | gLog.verbose("error=\(error)") 122 | } 123 | } 124 | 125 | func resetGridview() { 126 | self.gridView.removeFromSuperview() 127 | 128 | let eachItemSize = CGSize(width: ScreenWidth/CGFloat(VisibleColumnNum), height: itemHeight) 129 | gLog.verbose("eachItemSize=\(eachItemSize)") 130 | let columnItemSizeList = Array(count: SalesPerfomanceRowTitleList.count, repeatedValue: eachItemSize) 131 | gLog.verbose("columnItemSizeList=\(columnItemSizeList)") 132 | 133 | self.contentList = gCurUserItem.salesPerformanceList 134 | self.gridView = GridView(columnItemSizeList: columnItemSizeList, contentList: self.contentList) 135 | } 136 | 137 | 138 | } 139 | 140 | */ 141 | } -------------------------------------------------------------------------------- /swift/Thread/CrifanThread.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CrifanThread.swift 3 | // Crifan Li 4 | // Updated: 2017/09/28 5 | // 6 | 7 | import UIKit 8 | 9 | let MainThread:DispatchQueue = DispatchQueue.main 10 | 11 | let UserInteractiveThread:DispatchQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.userInteractive) 12 | let UserInitiatedThread:DispatchQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated) 13 | let DefaultThread:DispatchQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default) 14 | let UtilityThread:DispatchQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.utility) 15 | let BackgroundThread:DispatchQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.background) 16 | let UnspecifiedThread:DispatchQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.unspecified) 17 | 18 | 19 | /*************************************************************************** 20 | * GCD/Queue/Thread functions 21 | ***************************************************************************/ 22 | 23 | func delayDispatch(_ delayTimeInSec:Double, inThread:DispatchQueue, thingsTodo:@escaping ()->()) { 24 | let dispatchDelayTime = DispatchTime.now() + Double(Int64(delayTimeInSec * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) 25 | 26 | inThread.asyncAfter(deadline: dispatchDelayTime, execute: thingsTodo) 27 | } 28 | 29 | func delayDispatchInMainThread(_ delayTimeInSec:Double, thingsTodo:@escaping ()->()) { 30 | delayDispatch(delayTimeInSec, inThread: MainThread, thingsTodo: thingsTodo) 31 | } 32 | 33 | func dispatchMain_sync(_ delayTimeInSec:Double = 0.0, thingsTodo:@escaping ()->()) { 34 | delayDispatchInMainThread(delayTimeInSec, thingsTodo: thingsTodo) 35 | } 36 | 37 | func delayDispatchInBackgroundThread(_ delayTimeInSec:Double, thingsTodo:@escaping ()->()) { 38 | delayDispatch(delayTimeInSec, inThread: BackgroundThread, thingsTodo: thingsTodo) 39 | } 40 | 41 | func dispatchBackground_async(_ thingsTodo:@escaping ()->()) { 42 | BackgroundThread.async(execute: thingsTodo) 43 | } 44 | 45 | func dispatchUserInitiated_async(_ thingsTodo:@escaping ()->()) { 46 | UserInitiatedThread.async(execute: thingsTodo) 47 | } 48 | 49 | func dispatchMain_async(_ thingsTodo:@escaping ()->()) { 50 | MainThread.async(execute: thingsTodo) 51 | } 52 | 53 | -------------------------------------------------------------------------------- /swift/Thread/CrifanThreadDemo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CrifanThreadDemo.swift 3 | // CrifanLibSwift 4 | // 5 | // Created by licrifan on 16/8/3. 6 | // Copyright © 2016年 daryun.com. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | /* 13 | 14 | dispatchBackground_async({ 15 | getCustomerSource() 16 | getCarSerieList() 17 | getRegionList() 18 | getCustomerdefeat() 19 | }) 20 | 21 | dispatchMain_async({ 22 | let curTitle = self.normalTitle 23 | self.setTitle(curTitle, forState:UIControlState.Normal) 24 | }) 25 | 26 | */ -------------------------------------------------------------------------------- /swift/UI/Button/BadgeButton/BadgeButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BadgeButton.swift 3 | // Crifan Li 4 | // Updated: 2017/09/28 5 | // 6 | 7 | import UIKit 8 | import Cartography 9 | 10 | let BadgeButtonDefaultBadgeInt:Int = 0 11 | let BadgeButtonDefaultBadgeRadius:CGFloat = 7.0 12 | let BadgeButtonDefaultBadgeFillColor:UIColor = UIColor.redColor() 13 | let BadgeButtonDefaultBadgeFont:UIFont = UIFont.systemFontOfSize(10) 14 | let BadgeButtonDefaultBadgeMoveDown:CGFloat = 0 15 | let BadgeButtonDefaultBadgeMoveRight:CGFloat = 0 16 | 17 | class BadgeButton: UIButton { 18 | 19 | var badgeView:UIView 20 | 21 | var badgeInt:Int { 22 | didSet { 23 | gLog.info("badgeInt=\(badgeInt)") 24 | 25 | self.updateBadgeView() 26 | } 27 | } 28 | 29 | var badgeRadius:CGFloat { 30 | didSet { 31 | gLog.info("badgeRadius=\(badgeRadius)") 32 | self.updateBadgeView() 33 | } 34 | } 35 | 36 | var badgeFillColor:UIColor { 37 | didSet { 38 | gLog.info("badgeFillColor=\(badgeFillColor)") 39 | self.updateBadgeView() 40 | } 41 | } 42 | 43 | var badgeFont:UIFont { 44 | didSet { 45 | gLog.info("badgeFont=\(badgeFont)") 46 | self.updateBadgeView() 47 | } 48 | } 49 | 50 | var badgeMoveDown:CGFloat { 51 | didSet { 52 | gLog.info("badgeMoveDown=\(badgeMoveDown)") 53 | self.updateBadgeView() 54 | } 55 | } 56 | 57 | var badgeMoveRight:CGFloat { 58 | didSet { 59 | gLog.info("badgeMoveRight=\(badgeMoveRight)") 60 | self.updateBadgeView() 61 | } 62 | } 63 | 64 | override init(frame: CGRect) { 65 | self.badgeView = UIView() 66 | 67 | self.badgeInt = BadgeButtonDefaultBadgeInt 68 | self.badgeRadius = BadgeButtonDefaultBadgeRadius 69 | self.badgeFillColor = BadgeButtonDefaultBadgeFillColor 70 | self.badgeFont = BadgeButtonDefaultBadgeFont 71 | self.badgeMoveDown = BadgeButtonDefaultBadgeMoveDown 72 | self.badgeMoveRight = BadgeButtonDefaultBadgeMoveRight 73 | 74 | super.init(frame: frame) 75 | 76 | self.updateBadgeView() 77 | } 78 | 79 | convenience init(badgeInt:Int, badgeRadius:CGFloat = BadgeButtonDefaultBadgeRadius, badgeFillColor:UIColor = BadgeButtonDefaultBadgeFillColor, badgeFont:UIFont = BadgeButtonDefaultBadgeFont, badgeMoveDown:CGFloat = BadgeButtonDefaultBadgeMoveDown, badgeMoveRight:CGFloat = BadgeButtonDefaultBadgeMoveRight) { 80 | gLog.info("badgeInt=\(badgeInt), badgeRadius=\(badgeRadius), badgeFillColor=\(badgeFillColor), badgeFont=\(badgeFont), badgeMoveDown=\(badgeMoveDown), badgeMoveRight=\(badgeMoveRight)") 81 | 82 | self.init(frame: CGRectZero) 83 | 84 | self.badgeView = UIView() 85 | 86 | self.badgeInt = badgeInt 87 | self.badgeRadius = badgeRadius 88 | self.badgeFillColor = badgeFillColor 89 | self.badgeFont = badgeFont 90 | self.badgeMoveDown = badgeMoveDown 91 | self.badgeMoveRight = badgeMoveRight 92 | 93 | self.updateBadgeView() 94 | } 95 | 96 | required init?(coder aDecoder: NSCoder) { 97 | fatalError("init(coder:) has not been implemented") 98 | } 99 | 100 | func updateBadgeView() { 101 | gLog.verbose("badgeInt=\(badgeInt)") 102 | 103 | self.badgeView.removeFromSuperview() 104 | 105 | if badgeInt > 0 { 106 | self.badgeView.hidden = false 107 | self.badgeView = drawBadgeView(String(badgeInt), badgeRadius: self.badgeRadius, circleFillColor: self.badgeFillColor, badgeFont: self.badgeFont) 108 | 109 | gLog.debug("badgeView.frame=\(badgeView.frame)") 110 | //badgeView.frame=(0.0, 0.0, 17.0, 17.0) 111 | let badgeFrameSize = badgeView.frame.size 112 | //badgeFrameSize=(14.0, 14.0) 113 | self.addSubview(self.badgeView) 114 | constrain(self.badgeView) {badgeView in 115 | badgeView.top == badgeView.superview!.top - badgeFrameSize.height/2 + self.badgeMoveDown 116 | badgeView.right == badgeView.superview!.right - badgeFrameSize.width/2 + self.badgeMoveRight 117 | } 118 | } else { 119 | self.badgeView = UIView() 120 | self.badgeView.hidden = true 121 | } 122 | } 123 | } 124 | 125 | -------------------------------------------------------------------------------- /swift/UI/Button/BadgeButton/BadgeButtonDemo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BadgeButtonDemo.swift 3 | // Crifan Li 4 | // Updated: 2017/09/28 5 | // 6 | 7 | import UIKit 8 | 9 | func BadgeButtonDemo(){ 10 | let messageBadgeButton = BadgeButton() 11 | 12 | //top right messages 13 | messageBadgeButton.frame = CGRectMake(0, 0, NaviMessageImage.size.width, NaviMessageImage.size.height) 14 | messageBadgeButton.setImage(NaviMessageImage, forState: UIControlState.Normal) 15 | //messageBadgeButton.addTarget(self, action: #selector(self.showMessageVC), forControlEvents: UIControlEvents.TouchUpInside) 16 | messageBadgeButton.badgeMoveDown = 3 17 | messageBadgeButton.badgeMoveRight = 0 18 | 19 | let messageBarItem = UIBarButtonItem(customView: messageBadgeButton) 20 | 21 | SingletonMainVC().navigationItem.setRightBarButtonItem(messageBarItem, animated: false) 22 | 23 | //will auto update badge view 24 | messageBadgeButton.badgeInt = 4 25 | } -------------------------------------------------------------------------------- /swift/UI/Button/CommonButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommonButton.swift 3 | // Crifan Li 4 | // Updated: 2017/09/28 5 | // 6 | 7 | import UIKit 8 | import Cartography 9 | 10 | //let CommonButtonColor:UIColor = UIColor(hexString: "#CC0486")! 11 | let CommonButtonColor:UIColor = AppMainColor//UIColor(hexString: "#3D90C9")! 12 | 13 | 14 | let CommonButtonDisabledColor:UIColor = UIColor.gray 15 | 16 | let CommonButtonTitleFont:UIFont = UIFont.systemFont(ofSize: 15) 17 | 18 | let CommonButtonHeight:CGFloat = 30 19 | 20 | class CommonButton: UIButton { 21 | 22 | override init(frame: CGRect) { 23 | super.init(frame: frame) 24 | 25 | self.layer.borderColor = CommonButtonColor.cgColor 26 | self.layer.borderWidth = 1 27 | self.layer.cornerRadius = 5 28 | 29 | self.backgroundColor = UIColor.clear 30 | self.setTitleColor(CommonButtonColor, for: UIControlState()) 31 | self.setTitleColor(CommonButtonDisabledColor, for: UIControlState.disabled) 32 | 33 | self.titleLabel?.font = CommonButtonTitleFont 34 | } 35 | 36 | required init?(coder aDecoder: NSCoder) { 37 | fatalError("init(coder:) has not been implemented") 38 | } 39 | 40 | override var isEnabled: Bool{ 41 | didSet { 42 | if isEnabled { 43 | self.layer.borderColor = CommonButtonColor.cgColor 44 | } else { 45 | self.layer.borderColor = CommonButtonDisabledColor.cgColor 46 | } 47 | } 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /swift/UI/Button/CountdownButton/CountdownButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CountdownButton.swift 3 | // Crifan Li 4 | // Updated: 2017/09/28 5 | // 6 | 7 | import UIKit 8 | 9 | let CountdownButtonAutoStartCountdown:Bool = true 10 | let CountdownButtonNormalTitle:String = "发送验证码" 11 | let CountdownButtonTotalNum:Int = 60 12 | let CountdownButtonDisabledTitleColor:UIColor = UIColor.gray 13 | //let CountdownButtonDisabledTitleFormat:String = "发送验证码(%d秒)" 14 | let CountdownButtonDisabledTitleFormat:String = "(%d s)"//"重新发送(%d秒)" 15 | 16 | 17 | class CountdownButton: CommonButton { 18 | //class CountdownButton: UIButton { 19 | var autoStartCountdown:Bool 20 | var normalTitle:String 21 | var totalCountdownNum:Int 22 | var disabledTitleFormat:String 23 | 24 | var countdownTimer:Timer 25 | var countdownCurNum:Int 26 | 27 | override init(frame: CGRect) { 28 | self.autoStartCountdown = CountdownButtonAutoStartCountdown 29 | self.normalTitle = CountdownButtonNormalTitle 30 | self.totalCountdownNum = CountdownButtonTotalNum 31 | self.disabledTitleFormat = CountdownButtonDisabledTitleFormat 32 | 33 | self.countdownCurNum = 0 34 | self.countdownTimer = Timer() 35 | 36 | super.init(frame: frame) 37 | 38 | if self.autoStartCountdown { 39 | self.addTarget(self, action: #selector(self.startCountdown), for: UIControlEvents.touchUpInside) 40 | } 41 | 42 | self.setTitle(self.normalTitle, for:UIControlState()) 43 | } 44 | 45 | convenience init(normalTitle:String, 46 | autoStartCountdown:Bool = CountdownButtonAutoStartCountdown, 47 | totalCountdownNum:Int = CountdownButtonTotalNum, 48 | countingDownTitleFormat:String = CountdownButtonDisabledTitleFormat) { 49 | self.init(frame: CGRect.zero) 50 | 51 | self.autoStartCountdown = autoStartCountdown 52 | if !self.autoStartCountdown { 53 | self.removeTarget(self, action: #selector(self.startCountdown), for: UIControlEvents.touchUpInside) 54 | } 55 | 56 | self.normalTitle = normalTitle 57 | self.setTitle(self.normalTitle, for:UIControlState()) 58 | 59 | self.totalCountdownNum = totalCountdownNum 60 | 61 | self.disabledTitleFormat = countingDownTitleFormat 62 | } 63 | 64 | required init?(coder aDecoder: NSCoder) { 65 | fatalError("init(coder:) has not been implemented") 66 | } 67 | 68 | func startCountdown() { 69 | gLog.debug("") 70 | 71 | self.countdownCurNum = self.totalCountdownNum 72 | 73 | updateCountdownLabel() 74 | 75 | self.countdownTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector:#selector(self.updateCountdown), userInfo: nil, repeats: true) 76 | RunLoop.current.add(self.countdownTimer, forMode: RunLoopMode.commonModes) 77 | } 78 | 79 | func updateCountdown() { 80 | gLog.debug("self.countdownCurNum=\(self.countdownCurNum)") 81 | self.countdownCurNum -= 1 82 | 83 | if self.countdownCurNum <= 0 { 84 | self.countdownTimer.invalidate() 85 | self.countdownCurNum = 0 86 | } 87 | 88 | updateCountdownLabel() 89 | } 90 | 91 | func updateCountdownLabel(){ 92 | gLog.debug("self.countdownCurNum=\(self.countdownCurNum)") 93 | 94 | dispatchMain_async({ 95 | if self.countdownCurNum == 0 { 96 | self.isEnabled = true 97 | 98 | let curTitle = self.normalTitle 99 | self.setTitle(curTitle, for:UIControlState()) 100 | self.setTitle(curTitle, for:UIControlState.disabled) 101 | } else if self.countdownCurNum > 0 { 102 | self.isEnabled = false 103 | 104 | let curTitle = String(format: self.disabledTitleFormat, self.countdownCurNum) 105 | self.setTitle(curTitle, for:UIControlState.disabled) 106 | self.setTitle(curTitle, for:UIControlState()) 107 | } 108 | 109 | // self.updateSmsCodeButtonUI() 110 | }) 111 | } 112 | 113 | func stopCountdown(){ 114 | gLog.debug("stopCountdown: self.countdownCurNum=\(self.countdownCurNum)") 115 | 116 | self.countdownTimer.invalidate() 117 | 118 | self.countdownCurNum = 0 119 | 120 | updateCountdownLabel() 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /swift/UI/Button/CountdownButton/CountdownButtonDemo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CountdownButtonDemo.swift 3 | // Crifan Li 4 | // Updated: 2017/09/28 5 | // 6 | 7 | import UIKit 8 | 9 | func CountdownButtonDemo(){ 10 | //let getSmsCodeButton:CountdownButton = CountdownButton(normalTitle: "发送验证码", autoStartCounddown: false) 11 | let getSmsCodeButton:CountdownButton = CountdownButton(normalTitle: "发送验证码") 12 | print("getSmsCodeButton=\(getSmsCodeButton)") 13 | // self.addSubview(self.getSmsCodeButton) 14 | 15 | //if autoStartCounddown = true, then not need this 16 | getSmsCodeButton.startCountdown() 17 | 18 | getSmsCodeButton.stopCountdown() 19 | } -------------------------------------------------------------------------------- /swift/UI/Button/ImageLabelBadgeButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageLabelBadgeButton.swift 3 | // Xxx 4 | // 5 | // Created by licrifan on 16/6/17. 6 | // Copyright © 2016年 licrifan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Cartography 11 | 12 | enum ImageLabelPositionMode:Int { 13 | case ImageLeftLabelRight 14 | case ImageRightLabelLeft 15 | case ImageTopLabelBottom 16 | case ImageBottomLabelTop 17 | } 18 | 19 | class ImageLabelBadgeButton: BadgeButton { 20 | var curImageView:UIImageView 21 | var curTitleLabel:UILabel 22 | 23 | init(curImage:UIImage, 24 | curTitle:String, 25 | positionMode:ImageLabelPositionMode = .ImageLeftLabelRight, 26 | titleFont:UIFont = RightTextFieldTextFont, 27 | titleColor:UIColor = RightTextFieldTextColor, 28 | titleAlignment:NSTextAlignment = .Left, 29 | titleHeight:CGFloat = 0, 30 | paddingLeft:CGFloat = 0, 31 | paddingTop:CGFloat = 0, 32 | paddingRight:CGFloat = 0, 33 | paddingBottom:CGFloat = 0, 34 | paddingHorizontal:CGFloat = 0, 35 | paddingVertical:CGFloat = 0 36 | ) { 37 | gLog.debug("curImage=\(curImage), newTitle=\(curTitle), positionMode=\(positionMode), titleFont=\(titleFont), titleColor=\(titleColor), titleAlignment=\(titleAlignment), titleHeight=\(titleHeight), paddingLeft=\(paddingLeft), paddingTop=\(paddingTop), paddingRight=\(paddingRight), paddingBottom=\(paddingBottom), paddingHorizontal=\(paddingHorizontal), paddingVertical=\(paddingVertical)") 38 | 39 | self.curImageView = UIImageView() 40 | self.curTitleLabel = UILabel() 41 | 42 | super.init(frame: CGRectZero) 43 | 44 | let curImageSize = curImage.size 45 | gLog.debug("curImageSize=\(curImageSize)") 46 | 47 | //1. image view 48 | self.addSubview(self.curImageView) 49 | self.curImageView.image = curImage 50 | 51 | //2. title label 52 | self.addSubview(self.curTitleLabel) 53 | self.curTitleLabel.text = curTitle 54 | self.curTitleLabel.textAlignment = titleAlignment 55 | self.curTitleLabel.font = titleFont 56 | self.curTitleLabel.textColor = titleColor 57 | 58 | if positionMode == .ImageLeftLabelRight { 59 | constrain(self.curImageView) {curImageView in 60 | curImageView.left == curImageView.superview!.left + paddingLeft 61 | curImageView.width == curImageSize.width 62 | curImageView.centerY == curImageView.superview!.centerY 63 | } 64 | 65 | constrain(curTitleLabel, curImageView) {curTitleLabel, curImageView in 66 | curTitleLabel.left == curImageView.right + paddingHorizontal 67 | curTitleLabel.centerY == curTitleLabel.superview!.centerY 68 | curTitleLabel.width <= curTitleLabel.superview!.width 69 | } 70 | } else if positionMode == .ImageTopLabelBottom { 71 | constrain(self.curImageView) {curImageView in 72 | curImageView.top == curImageView.superview!.top + paddingTop 73 | curImageView.height == curImageSize.height 74 | curImageView.width == curImageSize.width 75 | curImageView.centerX == curImageView.superview!.centerX 76 | } 77 | 78 | constrain(curTitleLabel, curImageView) {curTitleLabel, curImageView in 79 | curTitleLabel.top == curImageView.bottom + paddingVertical 80 | if titleHeight != 0 { 81 | curTitleLabel.height == titleHeight 82 | } 83 | curTitleLabel.width <= curTitleLabel.superview!.width 84 | curTitleLabel.centerX == curTitleLabel.superview!.centerX 85 | } 86 | } 87 | 88 | } 89 | 90 | required init?(coder aDecoder: NSCoder) { 91 | fatalError("init(coder:) has not been implemented") 92 | } 93 | } -------------------------------------------------------------------------------- /swift/UI/CrifanUILabelUITextView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CrifanUILabel.swift 3 | // Crifan Li 4 | // Updated: 2017/09/28 5 | // 6 | 7 | import UIKit 8 | 9 | /*************************************************************************** 10 | * UITextView/UILabel Related functions 11 | ***************************************************************************/ 12 | 13 | //calc real text size for UITextView text 14 | func calcTexViewTextSize(text:String, font:UIFont, widthLimit:CGFloat) 15 | -> CGSize { 16 | let tmpTextView = UITextView(frame: CGRectZero) 17 | tmpTextView.font = font 18 | tmpTextView.text = text 19 | //Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Only run on the main thread!' 20 | let realTextSize = tmpTextView.sizeThatFits(CGSize(width: widthLimit, height: CGFloat.max)) 21 | //print("calculated realTextSize=\(realTextSize)") 22 | 23 | return realTextSize 24 | } 25 | 26 | //caculate text size for UILabel text 27 | func calcLabelTextSize(_ text:String, font:UIFont) -> CGSize { 28 | let textLabel:UILabel = UILabel() 29 | textLabel.text = text 30 | textLabel.font = font 31 | textLabel.sizeToFit() 32 | let labelTextSize:CGSize = textLabel.bounds.size 33 | 34 | return labelTextSize 35 | } 36 | 37 | //caculate text size for UILabel text 38 | func calcLabelTextSizeWithWidthLimit(_ text:String, font:UIFont, widthLimit:CGFloat) -> CGSize { 39 | let textLabel:UILabel = UILabel(frame: CGRect( 40 | x: 0, 41 | y: 0, 42 | width: widthLimit, 43 | height: CGFloat.greatestFiniteMagnitude)) 44 | textLabel.text = text 45 | textLabel.font = font 46 | textLabel.numberOfLines = 0 47 | //print("textLabel.frame=\(textLabel.frame)") 48 | textLabel.sizeToFit() 49 | //print("textLabel.frame=\(textLabel.frame)") 50 | 51 | let labelTextSize:CGSize = textLabel.bounds.size 52 | 53 | return labelTextSize 54 | } 55 | -------------------------------------------------------------------------------- /swift/UI/Image/CrifanUIImageDemo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CrifanUIImageDemo.swift 3 | // Xxx 4 | // 5 | // Created by licrifan on 16/7/16. 6 | // Copyright © 2016年 licrifan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CrifanUIImageDemo { 12 | 13 | init() { 14 | let ButtonNumberPerLine:Int = 3 15 | let ButtonWidthPercent:Float = 2.0/3.0 16 | let circleRadius:CGFloat = (UIScreen.mainScreen().bounds.width/CGFloat(ButtonNumberPerLine)) * CGFloat(ButtonWidthPercent) 17 | 18 | let darkerBlueColor = UIColor(red: 2.0/255.0, green: 174.0/255.0, blue: 240.0/255.0, alpha: 1) 19 | let lightBlueColor:UIColor = UIColor(red: 30.0/255.0, green: 175.0/255.0, blue: 235.0/255.0, alpha: 0.3) 20 | 21 | // let borderWidth:CGFloat = 2 22 | let borderWidth:CGFloat = 3 23 | let innerCircleRadius:CGFloat = circleRadius/4 24 | 25 | let normalGestureNodeImage = drawCircleImage(circleRadius, fillColor: UIColor.clearColor(), borderColor: UIColor.whiteColor(), borderWidth: borderWidth) 26 | print("normalGestureNodeImage=\(normalGestureNodeImage)") 27 | 28 | let selectedCircleImage:UIImage = drawCircileImageWithInnderCircle(circleRadius, fillColor: lightBlueColor, borderColor: darkerBlueColor, borderWidth: borderWidth, innerCircleRadius: innerCircleRadius, innerCircleFillColor: darkerBlueColor) 29 | let selectedGestureNodeImage = selectedCircleImage 30 | print("selectedGestureNodeImage=\(selectedGestureNodeImage)") 31 | 32 | 33 | } 34 | /* 35 | //draw single header image with text label 36 | func drawSingleCornerHeaderImageWithLabel(headerLabelString:String, headerLabelFont:UIFont, headerImageSize:CGFloat, backgroundColor:UIColor) -> UIImage { 37 | let rectHeaderImage:UIImage = drawRectHeaderImageWithLabel(headerLabelString, headerLabelFont: headerLabelFont, headerImageSize: headerImageSize, backgroundColor: backgroundColor) 38 | 39 | let cornerHeaderImage = drawCornerImage(rectHeaderImage, cornerRadius: SizeHeaderImageCornerRadius) 40 | 41 | return cornerHeaderImage 42 | } 43 | 44 | self.badgeView = drawBadgeView(realBadgeStr, badgeRadius: self.badgeRadius, circleFillColor: self.badgeFillColor, badgeFont: self.badgeFont) 45 | 46 | let backgroundRectImage:UIImage = drawRectangleImage( 47 | CGSizeMake(headerImageSize, headerImageSize), 48 | color: backgroundColor) 49 | 50 | //draw single header image with custom image 51 | //makesure align center for custom image 52 | func drawSingleCornerHeaderImageWithImage(customImage:UIImage, headerImageSize:CGFloat, backgroundColor:UIColor) -> UIImage { 53 | 54 | let backgroundRectImage:UIImage = drawRectangleImage( 55 | CGSizeMake(headerImageSize, headerImageSize), 56 | color: backgroundColor) 57 | 58 | var toMergeImageArr:[UIImage] = Array() 59 | toMergeImageArr.append(backgroundRectImage) 60 | toMergeImageArr.append(customImage) 61 | 62 | var drawPointArr:[CGPoint] = [CGPoint]() 63 | drawPointArr.append(CGPoint(x: 0, y: 0)) 64 | drawPointArr.append(CGPoint( 65 | x: (headerImageSize - customImage.size.width) / 2, 66 | y: (headerImageSize - customImage.size.height) / 2)) 67 | 68 | // let mergedRectHeaderImage:UIImage = mergeMultipleToSingleImage(backgroundRectImage, imageArr: toMergeImageArr, drawPointArr: drawPointArr) 69 | let mergedRectHeaderImage:UIImage = mergeMultipleToSingleImage(backgroundRectImage.size, imageArr: toMergeImageArr, drawPointArr: drawPointArr) 70 | 71 | let roundCornerHeaderImage:UIImage = drawCornerImage( 72 | mergedRectHeaderImage, 73 | cornerRadius: SizeHeaderImageCornerRadius) 74 | 75 | return roundCornerHeaderImage 76 | } 77 | 78 | 79 | 80 | let resizedHeaderImage:UIImage = eachOrigHeaderImage.resizeToWidth(headImageSize) 81 | 82 | 83 | self.pickedItem.scaledImage = self.pickedItem.originImage.compressImage(AttachmentMaxImageSize) 84 | 85 | let scaledImageData = self.pickedItem.scaledImage.toJpegData(0.8) 86 | */ 87 | 88 | } 89 | -------------------------------------------------------------------------------- /swift/UI/TableView/CrifanUITableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CrifanUITableViewCell.swift 3 | // Crifan Li 4 | // Updated: 2017/09/28 5 | // 6 | 7 | import UIKit 8 | 9 | /*************************************************************************** 10 | * Table View functions 11 | ***************************************************************************/ 12 | 13 | extension UITableViewCell { 14 | func hideSeparator() { 15 | let indentLargeEnoughToHide:CGFloat = 10000 16 | // indent large engough for separator(including cell' content) to hidden separator 17 | self.separatorInset = UIEdgeInsetsMake(0, indentLargeEnoughToHide, 0, 0) 18 | // adjust the cell's content to show normally 19 | self.indentationWidth = indentLargeEnoughToHide * -1 20 | // must add this, otherwise default is 0, now actual indentation = indentationWidth * indentationLevel = 10000 * 1 = -10000 21 | self.indentationLevel = 1 22 | } 23 | 24 | //make separator left align to edge 25 | func setSeparatorLeftAlign() { 26 | self.setSeparatorLeftRightAlign(-100, rightAlign: -100) 27 | } 28 | 29 | func setSeparatorLeftRightAlign(_ leftAlign:CGFloat = 0, rightAlign:CGFloat = 0){ 30 | self.separatorInset = UIEdgeInsetsMake(0, leftAlign, 0, rightAlign) 31 | 32 | //self.tableView.separatorStyle = UITableViewCellSeparatorStyle.SingleLine 33 | //self.separatorInset.left.add(leftAlign) // 34 | // self.separatorInset.left = 0//leftAlign 35 | // self.separatorInset.right = rightAlign 36 | self.layoutMargins = UIEdgeInsets.zero 37 | self.preservesSuperviewLayoutMargins = false 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /swift/UI/TableView/LeftLabelRightRadioButtonTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LeftLabelRightRadioButtonTableViewCell.swift 3 | // Xxx 4 | // 5 | // Created by licrifan on 16/6/10. 6 | // Copyright © 2016年 licrifan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Cartography 11 | import DLRadioButton 12 | 13 | let LeftLabelRightRadioButtonTableViewCellId:String = "LeftLabelRightRadioButtonTableViewCellId" 14 | 15 | let SingleRadioButtonWidth:CGFloat = 60 16 | 17 | let RadioButtonUnselectedImage = UIImage(named: "radio_button_unselected")! 18 | let RadioButtonSelectedImage = UIImage(named: "radio_button_selected")! 19 | 20 | 21 | class LeftLabelRightRadioButtonTableViewCell: LeftLabelRightTextfieldTableViewCell { 22 | var optionList:[String] 23 | 24 | init(editable:Bool = false, 25 | reuseIdentifier: String? = nil, 26 | leftLabelText:String = "", 27 | isMandatory:Bool = false, 28 | optionList:[String] = [String](), 29 | curSelectedIdx:Int = 0, 30 | buttonSelectedTarget:UIViewController? = nil, 31 | buttonSelectedHandler:Selector = nil 32 | ) { 33 | 34 | self.optionList = optionList 35 | 36 | if editable { 37 | super.init(editable: false, reuseIdentifier: reuseIdentifier ?? LeftLabelRightRadioButtonTableViewCellId, leftLabelText: leftLabelText, isMandatory: isMandatory) 38 | 39 | self.rightTextfield.hidden = true 40 | 41 | let firstRadioButton = DLRadioButton() 42 | radioButtonCommonSetup(firstRadioButton, buttonSelectedTarget: buttonSelectedTarget, buttonSelectedHandler: buttonSelectedHandler) 43 | firstRadioButton.tag = 0 44 | firstRadioButton.setTitle(self.optionList[0], forState: UIControlState.Normal) 45 | self.contentView.addSubview(firstRadioButton) 46 | 47 | if (curSelectedIdx >= 0) && (curSelectedIdx < self.optionList.count) { 48 | if firstRadioButton.tag == curSelectedIdx { 49 | firstRadioButton.selected = true 50 | } 51 | } 52 | 53 | // //for debug 54 | // firstRadioButton.backgroundColor = UIColor.greenColor() 55 | 56 | constrain(firstRadioButton) {firstRadioButton in 57 | firstRadioButton.left == firstRadioButton.superview!.left + RightTextfiledPaddingLeftToParent 58 | firstRadioButton.centerY == firstRadioButton.superview!.centerY 59 | firstRadioButton.width == SingleRadioButtonWidth 60 | } 61 | 62 | var otherButtonList = [DLRadioButton]() 63 | 64 | for curIdx in 1..= 0) && (curSelectedIdx < self.optionList.count) { 84 | if curRadioButton.tag == curSelectedIdx { 85 | curRadioButton.selected = true 86 | } 87 | } 88 | 89 | otherButtonList.append(curRadioButton) 90 | 91 | } 92 | 93 | firstRadioButton.otherButtons = otherButtonList 94 | 95 | 96 | } else { 97 | var rightTextFieldText = "" 98 | 99 | if self.optionList.count > 0 { 100 | if curSelectedIdx < 0 { 101 | rightTextFieldText = self.optionList[0] 102 | } else { 103 | if curSelectedIdx < self.optionList.count { 104 | rightTextFieldText = self.optionList[curSelectedIdx] 105 | } 106 | } 107 | } 108 | 109 | super.init(editable: editable, reuseIdentifier: reuseIdentifier ?? LeftLabelRightRadioButtonTableViewCellId, leftLabelText: leftLabelText, isMandatory: isMandatory, rightTextFieldText: rightTextFieldText) 110 | } 111 | } 112 | 113 | required init?(coder aDecoder: NSCoder) { 114 | fatalError("init(coder:) has not been implemented") 115 | } 116 | 117 | 118 | func radioButtonCommonSetup(radioButton:DLRadioButton, buttonSelectedTarget:UIViewController?, buttonSelectedHandler:Selector) { 119 | gLog.verbose("radioButton=\(radioButton), buttonSelectedTarget=\(buttonSelectedTarget), buttonSelectedHandler=\(buttonSelectedHandler)") 120 | //radioButton=>, buttonSelectedTarget=Optional(), buttonSelectedHandler=radioButtonSelected: 121 | 122 | // radioButton.titleLabel?.textAlignment = .Left 123 | radioButton.titleLabel!.font = RightTextFieldTextFont 124 | radioButton.setTitleColor(RightTextFieldTextColor, forState: UIControlState.Normal) 125 | radioButton.icon = RadioButtonUnselectedImage 126 | radioButton.iconSelected = RadioButtonSelectedImage 127 | radioButton.marginWidth = 0 128 | radioButton.iconStrokeWidth = 2 129 | // radioButton.iconSize = RadioButtonUnselectedImage.size.width 130 | radioButton.indicatorSize = 1 131 | radioButton.contentHorizontalAlignment = UIControlContentHorizontalAlignment.Left 132 | // radioButton.addTarget(self, action: #selector(self.radioButtonSelected(_:)), forControlEvents: UIControlEvents.TouchUpInside) 133 | 134 | if (buttonSelectedTarget != nil ) && (buttonSelectedHandler != nil) { 135 | // radioButton.addTarget(self, action: buttonSelectedHandler, forControlEvents: UIControlEvents.TouchUpInside) 136 | radioButton.addTarget(buttonSelectedTarget, action: buttonSelectedHandler, forControlEvents: UIControlEvents.TouchUpInside) 137 | } 138 | } 139 | 140 | // func radioButtonSelected(radioButton: DLRadioButton) { 141 | // gLog.verbose("radioButton=\(radioButton), text=\(radioButton.titleLabel!.text), tag=\(radioButton.tag)") 142 | // 143 | // if (radioButton.multipleSelectionEnabled) { 144 | // for button in radioButton.selectedButtons() { 145 | // gLog.verbose(String(format: "%@ is selected", button.titleLabel!.text!)); 146 | // } 147 | // } else { 148 | // gLog.verbose(String(format: "%@ is selected", radioButton.selectedButton()!.titleLabel!.text!)); 149 | // } 150 | // } 151 | } 152 | -------------------------------------------------------------------------------- /swift/UI/TableView/LeftLabelRightSegmentedControlTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LeftLabelRightSegmentedControlTableViewCell.swift 3 | // Xxx 4 | // 5 | // Created by licrifan on 16/6/10. 6 | // Copyright © 2016年 licrifan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Cartography 11 | 12 | let LeftLabelRightSegmentedControlTableViewCellId:String = "LeftLabelRightSegmentedControlTableViewCellId" 13 | 14 | //let LeftLabelRightSegmentedControlSegmentedControlColor:UIColor = UIColor(hexString: "#DC1B7F")! 15 | 16 | //let RightSegmentedFont:UIFont = UIFont.systemFontOfSize(9) 17 | let RightSegmentedFont:UIFont = UIFont.systemFontOfSize(11) 18 | 19 | class LeftLabelRightSegmentedControlTableViewCell: LeftLabelRightTextfieldTableViewCell { 20 | var segmentedControl :UISegmentedControl 21 | 22 | init(editable:Bool = false, 23 | reuseIdentifier: String? = nil, 24 | leftLabelText:String = "", 25 | isMandatory:Bool = false, 26 | optionList:[String] = [String](), 27 | curSelectedIdx:Int = 0, 28 | segmentedFont:UIFont = RightSegmentedFont 29 | ) { 30 | self.segmentedControl = UISegmentedControl() 31 | 32 | if editable { 33 | super.init(editable: editable, reuseIdentifier: reuseIdentifier ?? LeftLabelRightSegmentedControlTableViewCellId, leftLabelText: leftLabelText, isMandatory: isMandatory, rightTextFieldText: "") 34 | 35 | self.segmentedControl = UISegmentedControl(items: optionList) 36 | self.segmentedControl.selectedSegmentIndex = curSelectedIdx 37 | //self.segmentedControl.tintColor = LeftLabelRightSegmentedControlSegmentedControlColor 38 | self.segmentedControl.tintColor = CommonButtonColor 39 | self.segmentedControl.setTitleTextAttributes([NSFontAttributeName: segmentedFont], forState: .Normal) 40 | self.contentView.addSubview(segmentedControl) 41 | 42 | constrain(segmentedControl){segmentedControl in 43 | segmentedControl.left == segmentedControl.superview!.left + RightTextfiledPaddingLeftToParent 44 | segmentedControl.right == segmentedControl.superview!.right - AllPagePaddingX 45 | segmentedControl.centerY == segmentedControl.superview!.centerY 46 | //segmentedControl.height == 30 47 | segmentedControl.height == segmentedControl.superview!.height * 0.8 48 | } 49 | } else { 50 | var rightTextFieldText = "" 51 | 52 | if (curSelectedIdx >= 0) && (curSelectedIdx < optionList.count) { 53 | rightTextFieldText = optionList[curSelectedIdx] 54 | } 55 | 56 | super.init(editable: editable, reuseIdentifier: reuseIdentifier ?? LeftLabelRightSegmentedControlTableViewCellId, leftLabelText: leftLabelText, isMandatory: isMandatory, rightTextFieldText: rightTextFieldText) 57 | } 58 | } 59 | 60 | required init?(coder aDecoder: NSCoder) { 61 | fatalError("init(coder:) has not been implemented") 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /swift/UI/TableView/LeftLabelRightTextfieldTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LeftLabelRightTextfieldTableViewCell.swift 3 | // Xxx 4 | // 5 | // Created by licrifan on 16/6/10. 6 | // Copyright © 2016年 licrifan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Cartography 11 | 12 | let LeftLabelRightTextfieldTableViewCellId:String = "LeftLabelRightTextfieldTableViewCellId" 13 | 14 | 15 | let RightTextFieldTextColor:UIColor = CommonTextColorBlack 16 | let RightTextFieldTextFont:UIFont = UIFont.systemFontOfSize(15) 17 | //let CustomerDetailRightTextfiledPaddingLeftToParent:CGFloat = 90 18 | //let RightTextfiledPaddingLeftToParent:CGFloat = 90 19 | let RightTextfiledPaddingLeftToParent:CGFloat = 98 20 | 21 | class LeftLabelRightTextfieldTableViewCell: LeftLabelTableViewCell { 22 | var rightTextfield:UITextField 23 | var editable: Bool 24 | 25 | init(editable:Bool = false, 26 | reuseIdentifier: String? = nil, 27 | leftLabelText:String = "", 28 | isMandatory:Bool = false, 29 | rightTextFieldText:String = "", 30 | rightTextFieldPlaceholder:String = "", 31 | rightTextFieldTextColor:UIColor = RightTextFieldTextColor, 32 | rightTextFieldTextFont:UIFont = RightTextFieldTextFont, 33 | constrainToParentRight:CGFloat = AllPagePaddingX 34 | ) { 35 | self.editable = editable 36 | 37 | self.rightTextfield = UITextField() 38 | self.rightTextfield.text = rightTextFieldText 39 | self.rightTextfield.placeholder = rightTextFieldPlaceholder 40 | 41 | super.init(leftLabelText: leftLabelText, reuseIdentifier: reuseIdentifier ?? LeftLabelRightTextfieldTableViewCellId, isMandatory: isMandatory) 42 | 43 | if editable { 44 | self.rightTextfield.userInteractionEnabled = true 45 | self.rightTextfield.enabled = true 46 | } else { 47 | // self.rightTextfield.userInteractionEnabled = false 48 | self.rightTextfield.enabled = false 49 | } 50 | 51 | self.contentView.addSubview(self.rightTextfield) 52 | self.rightTextfield.textAlignment = .Left 53 | self.rightTextfield.font = rightTextFieldTextFont 54 | self.rightTextfield.textColor = rightTextFieldTextColor 55 | // constrain(self.rightTextfield, self.leftLabel, self.mandatoryImageview){rightTextfield, leftLabel, mandatoryImageview in 56 | constrain(self.rightTextfield){rightTextfield in 57 | rightTextfield.left == rightTextfield.superview!.left + RightTextfiledPaddingLeftToParent 58 | // if self.isMandatory { 59 | // rightTextfield.left >= mandatoryImageview.right 60 | // } else { 61 | // rightTextfield.left >= leftLabel.right 62 | // } 63 | 64 | rightTextfield.right == rightTextfield.superview!.right - constrainToParentRight 65 | rightTextfield.centerY == rightTextfield.superview!.centerY 66 | } 67 | 68 | } 69 | 70 | required init?(coder aDecoder: NSCoder) { 71 | fatalError("init(coder:) has not been implemented") 72 | } 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /swift/UI/TableView/TextviewTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextviewTableViewCell.swift 3 | // Xxx 4 | // 5 | // Created by licrifan on 16/6/10. 6 | // Copyright © 2016年 licrifan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Cartography 11 | import KMPlaceholderTextView 12 | 13 | let TextviewTableViewCellId:String = "TextviewTableViewCellId" 14 | let TextviewTVCHeight:CGFloat = 44 15 | 16 | 17 | class TextviewTableViewCell: UITableViewCell { 18 | var textview:KMPlaceholderTextView 19 | var editable:Bool 20 | 21 | init(editable:Bool = false, 22 | reuseIdentifier: String? = nil, 23 | rightTextviewText:String = "", 24 | rightTextviewFont:UIFont = RightTextFieldTextFont, 25 | rightTextviewColor:UIColor = RightTextFieldTextColor, 26 | rightTextviewPlaceholder:String = "" 27 | ) { 28 | self.editable = editable 29 | 30 | self.textview = KMPlaceholderTextView() 31 | self.textview.text = rightTextviewText 32 | self.textview.placeholder = rightTextviewPlaceholder 33 | 34 | super.init(style: .Default, reuseIdentifier: reuseIdentifier ?? TextviewTableViewCellId) 35 | 36 | self.contentView.backgroundColor = UIColor.whiteColor() 37 | 38 | self.textview.editable = self.editable 39 | self.textview.font = rightTextviewFont 40 | self.textview.textColor = rightTextviewColor 41 | self.contentView.addSubview(self.textview) 42 | 43 | constrain(self.textview){ textview in 44 | // textview.left == textview.superview!.left + AllPagePaddingX 45 | textview.top == textview.superview!.top 46 | textview.bottom == textview.superview!.bottom 47 | textview.left == textview.superview!.left 48 | textview.right == textview.superview!.right 49 | //textview.centerY == textview.superview!.centerY 50 | textview.height == TextviewTVCHeight 51 | } 52 | } 53 | 54 | required init?(coder aDecoder: NSCoder) { 55 | fatalError("init(coder:) has not been implemented") 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /swift/UI/ViewController/AutoMoveUpViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AutoMoveUpViewController.swift 3 | // Xxx 4 | // 5 | // Created by licrifan on 16/6/19. 6 | // Copyright © 2016年 licrifan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Cartography 11 | 12 | /* 13 | 1. when clicked some control(eg: textfield), will show keyboard 14 | then will automatically move up whole view 15 | 16 | 2. on the contrary, when end editing, keyboard will hide 17 | then will automatically mode down 18 | */ 19 | 20 | class AutoMoveUpViewController: UIViewController { 21 | var bottomView:UIView? 22 | 23 | var moveUpHeight:CGFloat 24 | 25 | var bottomToKeyboardTopPadding:CGFloat 26 | 27 | init(bottomView:UIView? = nil, bottomToKeyboardTopPadding:CGFloat = 10){ 28 | gLog.verbose("bottomView=\(bottomView), bottomToKeyboardTopPadding=\(bottomToKeyboardTopPadding)") 29 | 30 | self.bottomView = bottomView 31 | self.bottomToKeyboardTopPadding = bottomToKeyboardTopPadding 32 | 33 | self.moveUpHeight = 0 34 | 35 | super.init(nibName: nil, bundle: nil) 36 | } 37 | 38 | required init?(coder aDecoder: NSCoder) { 39 | fatalError("init(coder:) has not been implemented") 40 | } 41 | 42 | /*************************************************************************** 43 | * View Controller Functions 44 | ***************************************************************************/ 45 | 46 | override func viewDidLoad() { 47 | super.viewDidLoad() 48 | 49 | //listen keyboard to move up when keyboard show if necessary 50 | NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.keyboardWillShow(_:)), name:UIKeyboardWillShowNotification, object: nil) 51 | NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.keyboardWillHide(_:)), name:UIKeyboardWillHideNotification, object: nil) 52 | } 53 | 54 | /************************************************************************* 55 | * Current File Functions 56 | *************************************************************************/ 57 | 58 | func keyboardWillShow(notification: NSNotification) { 59 | gLog.verbose("notification=\(notification)") 60 | 61 | if self.view.frame.origin.y == 0 { 62 | let info : NSDictionary = notification.userInfo! 63 | if let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue().size { 64 | gLog.verbose("keyboardSize=\(keyboardSize)") 65 | 66 | let keyboardHeight = keyboardSize.height 67 | gLog.verbose("keyboardHeight=\(keyboardHeight)") 68 | 69 | gLog.verbose("self.view.frame=\(self.view.frame)") 70 | //(0.0, 0.0, 320.0, 568.0) 71 | 72 | var bottomEmptyHeight:CGFloat = 0 73 | 74 | if let curBottomView = self.bottomView { 75 | //has bottom view 76 | 77 | //calculate bottom empty height 78 | gLog.verbose("curBottomView=\(curBottomView.frame)") 79 | //(60.0, 215.0, 200.0, 30.0) 80 | 81 | guard let bottomViewSuperView = curBottomView.superview else { 82 | return 83 | } 84 | 85 | gLog.verbose("bottomViewSuperView=\(bottomViewSuperView)") 86 | 87 | let bottomViewFrame = bottomViewSuperView.convertRect(curBottomView.frame, toView: self.view) 88 | gLog.verbose("bottomViewFrame=\(bottomViewFrame)") 89 | //(60.0, 435.0, 200.0, 30.0) 90 | 91 | let bottomViewMaxYPos = bottomViewFrame.origin.y + bottomViewFrame.height 92 | gLog.verbose("bottomViewMaxYPos=\(bottomViewMaxYPos)") 93 | //465.0 94 | 95 | bottomEmptyHeight = self.view.frame.height - bottomViewMaxYPos 96 | } else { 97 | //not has bottom -> consider the whole self.view's bottom as bottom position 98 | bottomEmptyHeight = 0 99 | } 100 | 101 | gLog.verbose("keyboardHeight=\(keyboardHeight), bottomEmptyHeight=\(bottomEmptyHeight)") 102 | //103.0 103 | 104 | //for bottomEmptyHeight is 0 105 | //-> means must not pass in bottomView 106 | //-> whole view to move up 107 | //-> must clear bottomToKeyboardTopPadding 108 | //-> otherwise will see black gap 109 | if bottomEmptyHeight == 0 { 110 | bottomToKeyboardTopPadding = 0 111 | } 112 | 113 | if keyboardHeight > bottomEmptyHeight { 114 | //calculate height to move up 115 | moveUpHeight = keyboardHeight - bottomEmptyHeight + bottomToKeyboardTopPadding 116 | gLog.verbose("moveUpHeight=\(moveUpHeight)") 117 | //123.0 118 | 119 | self.view.frame.origin.y -= moveUpHeight 120 | } 121 | } 122 | } 123 | } 124 | 125 | func keyboardWillHide(notification: NSNotification) { 126 | gLog.verbose("notification=\(notification)") 127 | 128 | gLog.verbose("before hide: self.view.frame=\(self.view.frame)") //(0.0, -120.0, 320.0, 568.0) 129 | 130 | if self.view.frame.origin.y == (0 - moveUpHeight) { 131 | self.view.frame.origin.y += moveUpHeight 132 | } 133 | 134 | gLog.verbose("after hide: self.view.frame=\(self.view.frame)") //(0.0, 0.0, 320.0, 568.0) 135 | } 136 | 137 | } 138 | 139 | 140 | -------------------------------------------------------------------------------- /swift/UI/ViewController/AutoMoveUpViewController/AutoMoveUpViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AutoMoveUpViewController.swift 3 | // CrifanLibSwift 4 | // 5 | // Created by licrifan on 16/6/19. 6 | // Copyright © 2016年 licrifan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Cartography 11 | 12 | /* 13 | 1. when clicked some control(eg: textfield), will show keyboard 14 | then will automatically move up whole view 15 | 16 | 2. on the contrary, when end editing, keyboard will hide 17 | then will automatically mode down 18 | */ 19 | 20 | class AutoMoveUpViewController: UIViewController { 21 | var bottomView:UIView? 22 | 23 | var moveUpHeight:CGFloat 24 | 25 | var bottomToKeyboardTopPadding:CGFloat 26 | 27 | init(bottomView:UIView? = nil, bottomToKeyboardTopPadding:CGFloat = 10){ 28 | gLog.verbose("bottomView=\(bottomView), bottomToKeyboardTopPadding=\(bottomToKeyboardTopPadding)") 29 | 30 | self.bottomView = bottomView 31 | self.bottomToKeyboardTopPadding = bottomToKeyboardTopPadding 32 | 33 | self.moveUpHeight = 0 34 | 35 | super.init(nibName: nil, bundle: nil) 36 | } 37 | 38 | required init?(coder aDecoder: NSCoder) { 39 | fatalError("init(coder:) has not been implemented") 40 | } 41 | 42 | /*************************************************************************** 43 | * View Controller Functions 44 | ***************************************************************************/ 45 | 46 | override func viewDidLoad() { 47 | super.viewDidLoad() 48 | 49 | //listen keyboard to move up when keyboard show if necessary 50 | NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.keyboardWillShow(_:)), name:UIKeyboardWillShowNotification, object: nil) 51 | NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.keyboardWillHide(_:)), name:UIKeyboardWillHideNotification, object: nil) 52 | } 53 | 54 | /************************************************************************* 55 | * Current File Functions 56 | *************************************************************************/ 57 | 58 | func keyboardWillShow(notification: NSNotification) { 59 | gLog.verbose("notification=\(notification)") 60 | 61 | if self.view.frame.origin.y == 0 { 62 | let info : NSDictionary = notification.userInfo! 63 | if let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue().size { 64 | gLog.verbose("keyboardSize=\(keyboardSize)") 65 | 66 | let keyboardHeight = keyboardSize.height 67 | gLog.verbose("keyboardHeight=\(keyboardHeight)") 68 | //keyboardHeight=216.0 69 | //keyboardHeight=253.0 70 | 71 | gLog.verbose("self.view.frame=\(self.view.frame)") 72 | //(0.0, 0.0, 320.0, 568.0) 73 | 74 | var bottomEmptyHeight:CGFloat = 0 75 | 76 | if let curBottomView = self.bottomView { 77 | //has bottom view 78 | 79 | //calculate bottom empty height 80 | gLog.verbose("curBottomView=\(curBottomView.frame)") 81 | //(60.0, 215.0, 200.0, 30.0) 82 | 83 | guard let bottomViewSuperView = curBottomView.superview else { 84 | return 85 | } 86 | 87 | gLog.verbose("bottomViewSuperView=\(bottomViewSuperView)") 88 | 89 | let bottomViewFrame = bottomViewSuperView.convertRect(curBottomView.frame, toView: self.view) 90 | gLog.verbose("bottomViewFrame=\(bottomViewFrame)") 91 | //(60.0, 435.0, 200.0, 30.0) 92 | 93 | let bottomViewMaxYPos = bottomViewFrame.origin.y + bottomViewFrame.height 94 | gLog.verbose("bottomViewMaxYPos=\(bottomViewMaxYPos)") 95 | //465.0 96 | 97 | bottomEmptyHeight = self.view.frame.height - bottomViewMaxYPos 98 | } else { 99 | //not has bottom -> consider the whole self.view's bottom as bottom position 100 | bottomEmptyHeight = 0 101 | } 102 | 103 | gLog.verbose("keyboardHeight=\(keyboardHeight), bottomEmptyHeight=\(bottomEmptyHeight)") 104 | //103.0 105 | 106 | //for bottomEmptyHeight is 0 107 | //-> means must not pass in bottomView 108 | //-> whole view to move up 109 | //-> must clear bottomToKeyboardTopPadding 110 | //-> otherwise will see black gap 111 | if bottomEmptyHeight == 0 { 112 | bottomToKeyboardTopPadding = 0 113 | } 114 | 115 | if keyboardHeight > bottomEmptyHeight { 116 | //calculate height to move up 117 | moveUpHeight = keyboardHeight - bottomEmptyHeight + bottomToKeyboardTopPadding 118 | gLog.verbose("moveUpHeight=\(moveUpHeight)") 119 | //123.0 120 | 121 | self.view.frame.origin.y -= moveUpHeight 122 | 123 | // self.view.setNeedsLayout() 124 | // self.view.layoutSubviews() 125 | } 126 | } 127 | } 128 | } 129 | 130 | func keyboardWillHide(notification: NSNotification) { 131 | gLog.verbose("notification=\(notification)") 132 | 133 | gLog.verbose("before hide: self.view.frame=\(self.view.frame)") //(0.0, -120.0, 320.0, 568.0) 134 | 135 | if self.view.frame.origin.y == (0 - moveUpHeight) { 136 | self.view.frame.origin.y += moveUpHeight 137 | } 138 | 139 | gLog.verbose("after hide: self.view.frame=\(self.view.frame)") //(0.0, 0.0, 320.0, 568.0) 140 | } 141 | 142 | } 143 | 144 | 145 | -------------------------------------------------------------------------------- /swift/UI/ViewController/CrifanViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CrifanViewController.swift 3 | // Crifan Li 4 | // Updated: 2017/09/28 5 | // 6 | 7 | import UIKit 8 | 9 | /*************************************************************************** 10 | * View Functions 11 | ***************************************************************************/ 12 | 13 | extension UIApplication { 14 | class func topViewController(_ base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? { 15 | if let nav = base as? UINavigationController { 16 | return topViewController(nav.visibleViewController) 17 | } 18 | if let tab = base as? UITabBarController { 19 | if let selected = tab.selectedViewController { 20 | return topViewController(selected) 21 | } 22 | } 23 | if let presented = base?.presentedViewController { 24 | return topViewController(presented) 25 | } 26 | return base 27 | } 28 | 29 | class var topViewControllerInStack: UIViewController? { 30 | var topVcInStack:UIViewController? = nil 31 | 32 | if let rootVc = UIApplication.shared.keyWindow?.rootViewController { 33 | if rootVc is UINavigationController { 34 | let rootNavi = rootVc as! UINavigationController 35 | if let lastVc = rootNavi.viewControllers.last { 36 | // 37 | topVcInStack = lastVc 38 | } 39 | } 40 | } 41 | 42 | return topVcInStack 43 | } 44 | } 45 | 46 | extension UIViewController { 47 | var isCurrentShowing: Bool { 48 | print("self=\(self)") 49 | //self= 50 | 51 | var isCurrentShow = false 52 | 53 | //check whether is current showing UI 54 | if let topVC = UIApplication.topViewController() { 55 | // print("topVC=\(topVC)") 56 | //topVC= 57 | 58 | if topVC == self { 59 | isCurrentShow = true 60 | } 61 | } 62 | 63 | return isCurrentShow 64 | } 65 | 66 | func showAlert(_ title:String, message:String? = nil) { 67 | // print("title=\(title), message=\(message)") 68 | 69 | dispatchMain_async({ 70 | let alertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert) 71 | let sureAlertAction = UIAlertAction(title: "确定", style: UIAlertActionStyle.destructive, handler: nil) 72 | alertController.addAction(sureAlertAction) 73 | 74 | if self.isCurrentShowing { 75 | print("self=\(self)") 76 | self.present(alertController, animated: true, completion: nil) 77 | } 78 | }) 79 | } 80 | 81 | func getCallerViewController() -> UIViewController? { 82 | var calerVc:UIViewController? = nil 83 | 84 | if let naviViewControllers = self.navigationController?.viewControllers{ 85 | if let lastVc = naviViewControllers.last { 86 | calerVc = lastVc 87 | //Optional() 88 | } 89 | } 90 | 91 | return calerVc 92 | } 93 | 94 | var hasExisted: Bool { 95 | var existedVc = false 96 | if let naviController = self.navigationController{ 97 | for eachVc in naviController.viewControllers { 98 | if eachVc == self { 99 | print("found self=\(self) in navi viewControllers") 100 | existedVc = true 101 | break 102 | } 103 | } 104 | } 105 | 106 | return existedVc 107 | } 108 | 109 | func doShowViewController(_ vcToShow:UIViewController) { 110 | print("self=\(self), vcToShow=\(vcToShow)") 111 | //doShowViewController self=, vcToShow= 112 | 113 | if vcToShow.hasExisted { 114 | //pop to that view controller 115 | //print("curVc.navigationController?.viewControllers=\(curVc.navigationController?.viewControllers)") 116 | _ = self.navigationController?.popToViewController(vcToShow, animated: true) 117 | // print("curVc.navigationController?.viewControllers=\(curVc.navigationController?.viewControllers)") 118 | } else { 119 | self.show(vcToShow, sender: self) 120 | } 121 | } 122 | 123 | func modelPresentViewController(_ vcToShow:UIViewController, animated:Bool = true, forceInMainThread:Bool = false) { 124 | print("vcToShow=\(vcToShow), animated=\(animated), forceInMainThread=\(forceInMainThread)") 125 | 126 | vcToShow.modalPresentationStyle = .custom 127 | 128 | if forceInMainThread { 129 | //sometime for workaround a bug, such as: 130 | //http://openradar.appspot.com/19563577 131 | //need run in main thread 132 | dispatchMain_async({ 133 | self.present(vcToShow, animated: animated, completion: nil) 134 | }) 135 | } else { 136 | self.present(vcToShow, animated: animated, completion: nil) 137 | } 138 | } 139 | } 140 | 141 | -------------------------------------------------------------------------------- /swift/UI/ViewController/ImageViewerController/ImageViewerController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageViewerController.swift 3 | // CrifanLibSwift 4 | // 5 | // Created by Crifan Li on 16/6/30. 6 | // Copyright © 2016年 licrifan. All rights reserved. 7 | // 8 | // show image 9 | // double to zoom in 10 | // when exceed max size to back to fit size 11 | // allow pinch to scroll 12 | 13 | import UIKit 14 | 15 | class ImageViewerController: UIViewController, UIScrollViewDelegate { 16 | var curImage:UIImage 17 | 18 | var imageView: UIImageView 19 | 20 | var scrollView:UIScrollView 21 | 22 | init(originImage:UIImage) { 23 | self.curImage = originImage 24 | self.imageView = UIImageView(image: self.curImage) 25 | 26 | self.scrollView = UIScrollView() 27 | 28 | super.init(nibName: nil, bundle: nil) 29 | } 30 | 31 | required init?(coder aDecoder: NSCoder) { 32 | fatalError("init(coder:) has not been implemented") 33 | } 34 | 35 | override func viewDidLoad() { 36 | super.viewDidLoad() 37 | self.view.backgroundColor = UIColor(hexString: "#999999", alpha: 0.5)! 38 | 39 | self.scrollView.addSubview(self.imageView) 40 | self.view.addSubview(self.scrollView) 41 | 42 | self.scrollView.frame = CGRectMake( 43 | 0, 44 | 0, 45 | self.view.frame.width, 46 | self.view.frame.height - 0 47 | ) 48 | 49 | let imageSize = self.imageView.image!.size 50 | // print("imageSize=\(imageSize)") //(1359.0, 901.0) 51 | 52 | self.scrollView.contentSize = imageSize 53 | 54 | self.scrollView.bounces = false 55 | self.scrollView.showsHorizontalScrollIndicator = false 56 | self.scrollView.showsVerticalScrollIndicator = false 57 | self.scrollView.userInteractionEnabled = true 58 | self.scrollView.delegate = self 59 | self.scrollView.bouncesZoom = false 60 | self.scrollView.scrollsToTop = false 61 | self.scrollView.backgroundColor = UIColor.blackColor() 62 | let scrollViewFrame = scrollView.frame 63 | // print("scrollViewFrame=\(scrollViewFrame)") //(0.0, 0.0, 375.0, 667.0) 64 | let scaleWidth = scrollViewFrame.size.width / scrollView.contentSize.width 65 | // print("scaleWidth=\(scaleWidth)") //0.275938189845475 66 | let scaleHeight = scrollViewFrame.size.height / scrollView.contentSize.height 67 | // print("scaleHeight=\(scaleHeight)") //0.740288568257492 68 | let minScale = min(scaleWidth, scaleHeight) 69 | // print("minScale=\(minScale)") //0.275938189845475 70 | 71 | self.scrollView.minimumZoomScale = minScale 72 | self.scrollView.maximumZoomScale = 1.0 73 | self.scrollView.zoomScale = minScale 74 | 75 | centerScrollViewContents() 76 | 77 | let doubleTap = UITapGestureRecognizer(target: self, action: #selector(self.doubleTapped(_:))) 78 | doubleTap.numberOfTapsRequired = 2 79 | doubleTap.numberOfTouchesRequired = 1 80 | self.scrollView.addGestureRecognizer(doubleTap) 81 | 82 | let singleTap = UITapGestureRecognizer(target: self, action: #selector(self.singleTap(_:))) 83 | singleTap.numberOfTapsRequired = 1 84 | singleTap.numberOfTouchesRequired = 1 85 | self.scrollView.addGestureRecognizer(singleTap) 86 | singleTap.requireGestureRecognizerToFail(doubleTap) 87 | } 88 | 89 | override func didReceiveMemoryWarning() { 90 | super.didReceiveMemoryWarning() 91 | } 92 | 93 | func centerScrollViewContents() { 94 | let boundsSize = self.scrollView.bounds.size 95 | // print("boundsSize=\(boundsSize)") 96 | var contentsFrame = self.imageView.frame 97 | // print("contentsFrame=\(contentsFrame)") 98 | 99 | if contentsFrame.size.width < boundsSize.width { 100 | contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0 101 | } else { 102 | contentsFrame.origin.x = 0.0 103 | } 104 | 105 | if contentsFrame.size.height < boundsSize.height { 106 | contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0 107 | } else { 108 | contentsFrame.origin.y = 0.0 109 | } 110 | 111 | // print("contentsFrame=\(contentsFrame)") 112 | self.imageView.frame = contentsFrame 113 | } 114 | 115 | func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? { 116 | return self.imageView 117 | } 118 | 119 | func scrollViewDidZoom(scrollView: UIScrollView) { 120 | centerScrollViewContents() 121 | } 122 | 123 | func doubleTapped(recognizer: UITapGestureRecognizer) { 124 | if (self.scrollView.zoomScale == self.scrollView.maximumZoomScale) { 125 | self.scrollView.setZoomScale(self.scrollView.minimumZoomScale, animated: true) 126 | } else { 127 | let pointInView = recognizer.locationInView(self.imageView) 128 | // print("pointInView=\(pointInView)") //(898.155653734425, 425.882443769075) 129 | 130 | var newZoomScale = scrollView.zoomScale * 1.5 131 | // print("newZoomScale=\(newZoomScale)") //0.986187634979701 132 | 133 | newZoomScale = min(newZoomScale, scrollView.maximumZoomScale) 134 | // print("newZoomScale=\(newZoomScale)") //0.986187634979701 135 | 136 | let scrollViewSize = scrollView.bounds.size 137 | // print("scrollViewSize=\(scrollViewSize)") //(375.0, 603.0) 138 | 139 | let w = scrollViewSize.width / newZoomScale 140 | let h = scrollViewSize.height / newZoomScale 141 | let x = pointInView.x - (w / 2.0) 142 | let y = pointInView.y - (h / 2.0) 143 | 144 | let rectToZoomTo = CGRectMake(x, y, w, h); 145 | // print("rectToZoomTo=\(rectToZoomTo)") //(708.029562766088, 120.159689491989, 380.252181936674, 611.445508554172) 146 | 147 | scrollView.zoomToRect(rectToZoomTo, animated: true) 148 | } 149 | } 150 | 151 | func singleTap(recognizer:UITapGestureRecognizer){ 152 | dispatchMain_async({ 153 | self.dismissViewControllerAnimated(true, completion: nil) 154 | }) 155 | } 156 | 157 | } 158 | 159 | -------------------------------------------------------------------------------- /swift/UI/ViewController/LeftLabelRightSelectableTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LeftLabelRightSelectableTableViewCell.swift 3 | // Xxx 4 | // 5 | // Created by licrifan on 16/6/10. 6 | // Copyright © 2016年 licrifan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Cartography 11 | 12 | let LeftLabelRightSelectableTableViewCellId:String = "LeftLabelRightSelectableTableViewCellId" 13 | 14 | class LeftLabelRightSelectableTableViewCell: LeftLabelRightTextfieldTableViewCell { 15 | var selectable:Bool 16 | var itemList:[String] 17 | 18 | init(selectable:Bool = false, 19 | reuseIdentifier: String? = nil, 20 | leftLabelText:String = "", 21 | isMandatory:Bool = false, 22 | rightTextFieldText:String = "", 23 | rightTextFieldTextColor:UIColor = RightTextFieldTextColor, 24 | rightTextFieldTextFont:UIFont = RightTextFieldTextFont, 25 | itemList:[String] = [String]() 26 | ) { 27 | self.selectable = selectable 28 | self.itemList = itemList 29 | 30 | // super.init(editable: editable, reuseIdentifier: reuseIdentifier ?? LeftLabelRightSelectableTableViewCellId, leftLabelText: leftLabelText, isMandatory: isMandatory, rightTextFieldText: rightTextFieldText) 31 | super.init(editable: false, reuseIdentifier: reuseIdentifier ?? LeftLabelRightSelectableTableViewCellId, leftLabelText: leftLabelText, isMandatory: isMandatory, rightTextFieldText: rightTextFieldText, rightTextFieldTextColor: rightTextFieldTextColor, rightTextFieldTextFont: rightTextFieldTextFont) 32 | 33 | if self.selectable { 34 | self.accessoryType = .DisclosureIndicator 35 | } 36 | } 37 | 38 | required init?(coder aDecoder: NSCoder) { 39 | fatalError("init(coder:) has not been implemented") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /swift/UI/ViewController/LeftLabelRightTextviewTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LeftLabelRightTextviewTableViewCell.swift 3 | // Xxx 4 | // 5 | // Created by licrifan on 16/6/18. 6 | // Copyright © 2016年 licrifan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Cartography 11 | import KMPlaceholderTextView 12 | 13 | let LeftLabelRightTextviewTableViewCellId:String = "LeftLabelRightTextviewTableViewCellId" 14 | 15 | let RightTextviewHeight:CGFloat = 60 16 | 17 | class LeftLabelRightTextviewTableViewCell: LeftLabelTableViewCell { 18 | var rightTextview:KMPlaceholderTextView 19 | var editable: Bool 20 | 21 | init(editable:Bool = false, 22 | reuseIdentifier: String? = nil, 23 | leftLabelText:String = "", 24 | isMandatory:Bool = false, 25 | rightTextviewText:String = "", 26 | rightTextviewPlaceholder:String = "", 27 | rightTextviewTextColor:UIColor = RightTextFieldTextColor, 28 | rightTextviewTextFont:UIFont = RightTextFieldTextFont 29 | ) { 30 | self.editable = editable 31 | self.rightTextview = KMPlaceholderTextView() 32 | 33 | super.init(leftLabelText: leftLabelText, reuseIdentifier: reuseIdentifier ?? LeftLabelRightTextviewTableViewCellId, isMandatory: isMandatory) 34 | 35 | self.rightTextview.text = rightTextviewText 36 | self.rightTextview.placeholder = rightTextviewPlaceholder 37 | 38 | if !self.editable { 39 | self.rightTextview.editable = false 40 | } 41 | 42 | self.contentView.addSubview(self.rightTextview) 43 | constrain(self.rightTextview) {rightTextview in 44 | rightTextview.left == rightTextview.superview!.left + RightTextfiledPaddingLeftToParent 45 | rightTextview.right <= rightTextview.superview!.right 46 | rightTextview.height == RightTextviewHeight 47 | rightTextview.bottom == rightTextview.superview!.bottom 48 | } 49 | 50 | } 51 | 52 | required init?(coder aDecoder: NSCoder) { 53 | fatalError("init(coder:) has not been implemented") 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /swift/UI/ViewController/LeftLabelTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LeftLabelTableViewCell.swift 3 | // Xxx 4 | // 5 | // Created by licrifan on 16/6/8. 6 | // Copyright © 2016年 licrifan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Cartography 11 | 12 | let LeftLabelTableViewCellId:String = "LeftLabelTableViewCellId" 13 | 14 | let LeftLabelTVCLabelColor:UIColor = UIColor(hexString: "#999999")! 15 | let LeftLabelTVCLabelFont:UIFont = UIFont.systemFontOfSize(15) 16 | let LeftLabelTVCMaxWidth:CGFloat = 70 17 | 18 | let LeftLabelTVCBackgroundColor:UIColor = UIColor.whiteColor() 19 | 20 | class LeftLabelTableViewCell: UITableViewCell { 21 | var leftLabel:UILabel 22 | var mandatoryImageview:UIImageView 23 | var isMandatory:Bool 24 | 25 | init(leftLabelText:String, 26 | reuseIdentifier:String? = nil, 27 | leftLabelColor:UIColor = LeftLabelTVCLabelColor, 28 | leftLabelFont:UIFont = LeftLabelTVCLabelFont, 29 | isMandatory:Bool = false, 30 | leftLabelMaxWidth:CGFloat = LeftLabelTVCMaxWidth 31 | ){ 32 | self.leftLabel = UILabel() 33 | self.mandatoryImageview = UIImageView() 34 | self.isMandatory = isMandatory 35 | 36 | super.init(style: UITableViewCellStyle.Default, reuseIdentifier: reuseIdentifier) 37 | self.selectionStyle = UITableViewCellSelectionStyle.None 38 | 39 | self.imageView?.image = nil 40 | self.imageView?.hidden = true 41 | self.textLabel?.text = nil 42 | self.textLabel?.hidden = true 43 | 44 | self.accessoryType = UITableViewCellAccessoryType.None 45 | self.accessoryView = nil 46 | 47 | self.backgroundColor = LeftLabelTVCBackgroundColor 48 | 49 | 50 | self.isMandatory = isMandatory 51 | 52 | self.leftLabel.numberOfLines = 0 53 | 54 | self.leftLabel.text = leftLabelText 55 | self.leftLabel.textColor = leftLabelColor 56 | self.leftLabel.font = leftLabelFont 57 | self.contentView.addSubview(self.leftLabel) 58 | constrain(self.leftLabel){ leftLabel in 59 | leftLabel.left == leftLabel.superview!.left + AllPagePaddingX 60 | //leftLabel.width <= LeftLabelTVCMaxWidth 61 | leftLabel.width <= leftLabelMaxWidth 62 | leftLabel.centerY == leftLabel.superview!.centerY 63 | 64 | //leftLabel.right <= leftLabel.superview!.right + RightTextfiledPaddingLeftToParent 65 | } 66 | 67 | if self.isMandatory { 68 | self.mandatoryImageview.hidden = false 69 | 70 | let mandatoryImage:UIImage = UIImage(named: "important_star")! 71 | self.mandatoryImageview.image = mandatoryImage 72 | self.contentView.addSubview(self.mandatoryImageview) 73 | constrain(self.mandatoryImageview, self.leftLabel){ mandatoryImageview, leftLabel in 74 | mandatoryImageview.left == leftLabel.right + 5 75 | mandatoryImageview.centerY == leftLabel.centerY 76 | mandatoryImageview.width == mandatoryImage.size.width 77 | mandatoryImageview.height == mandatoryImage.size.height 78 | 79 | //mandatoryImageview.right <= mandatoryImageview.superview!.right + RightTextfiledPaddingLeftToParent 80 | } 81 | } else { 82 | self.mandatoryImageview.hidden = true 83 | } 84 | } 85 | 86 | required init?(coder aDecoder: NSCoder) { 87 | fatalError("init(coder:) has not been implemented") 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /swift/UI/ViewController/SelectItemViewController/SelectItemViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SelectItemViewController.swift 3 | // CrifanLibSwift 4 | // 5 | // Created by licrifan on 16/6/15. 6 | // Copyright © 2016年 licrifan. All rights reserved. 7 | // 8 | // show a list 9 | // allow click to select a single row 10 | 11 | import UIKit 12 | import Cartography 13 | 14 | let SelectTableViewPaddingX:CGFloat = 15 15 | let SelectTableViewCellHeight:CGFloat = 44 16 | 17 | let SelectItemTableViewCellId:String = "SelectItemTableViewCellId" 18 | 19 | 20 | class SelectItemViewController: TapToHideViewController, UITableViewDelegate, UITableViewDataSource { 21 | var selectTableView:UITableView 22 | 23 | var selectItemList:[String] 24 | 25 | var curSelectIdx:Int 26 | var returnSelectIdx:Int 27 | 28 | var completionHadler:((Int) -> Void)? 29 | 30 | init(curSelectIdx:Int = Int.InvalidIndex, selectItemList:[String] = [String](), completionHadler:((Int) -> Void)? = nil) { 31 | gLog.verbose("curSelectIdx=\(curSelectIdx), selectItemList=\(selectItemList), completionHadler=\(completionHadler)") 32 | 33 | self.curSelectIdx = curSelectIdx 34 | self.selectItemList = selectItemList 35 | self.selectTableView = UITableView() 36 | self.completionHadler = completionHadler 37 | 38 | self.returnSelectIdx = Int.InvalidIndex 39 | 40 | super.init(notHideView: selectTableView) 41 | 42 | self.hideVCHandler = self.callCompletionHadler 43 | } 44 | 45 | required init?(coder aDecoder: NSCoder) { 46 | fatalError("init(coder:) has not been implemented") 47 | } 48 | 49 | override func viewDidLoad() { 50 | super.viewDidLoad() 51 | 52 | self.view.backgroundColor = CommonSelectionViewAlphaBkgColor 53 | 54 | let rowNum:Int = self.selectItemList.count 55 | var selectTableViewHeight:CGFloat = SelectTableViewCellHeight * CGFloat(rowNum) 56 | gLog.verbose("rowNum=\(rowNum), selectTableViewHeight=\(selectTableViewHeight)") 57 | 58 | if selectTableViewHeight > ScreenHeight { 59 | selectTableViewHeight = ScreenHeight 60 | } 61 | 62 | self.selectTableView.delegate = self 63 | self.selectTableView.dataSource = self 64 | self.selectTableView.rowHeight = SelectTableViewCellHeight 65 | self.selectTableView.layer.cornerRadius = 20 66 | self.selectTableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: SelectItemTableViewCellId) 67 | self.view.addSubview(self.selectTableView) 68 | constrain(self.selectTableView){ selectTableView in 69 | selectTableView.left == selectTableView.superview!.left + SelectTableViewPaddingX 70 | selectTableView.right == selectTableView.superview!.right - SelectTableViewPaddingX 71 | selectTableView.height == selectTableViewHeight 72 | selectTableView.centerY == selectTableView.superview!.centerY 73 | } 74 | 75 | if self.curSelectIdx.isValidIndex { 76 | let selectedIndexPath = NSIndexPath(forRow: self.curSelectIdx, inSection: 0) 77 | gLog.verbose("selectedIndexPath=\(selectedIndexPath) self.curSelectIdx=\(self.curSelectIdx)") 78 | 79 | self.selectTableView.selectRowAtIndexPath(selectedIndexPath, animated: false, scrollPosition: UITableViewScrollPosition.Middle) 80 | } 81 | } 82 | 83 | /************************************************************************* 84 | * UITableViewDelegate Functions 85 | *************************************************************************/ 86 | 87 | /************************************************************************* 88 | * UITableViewDataSource Functions 89 | *************************************************************************/ 90 | 91 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 92 | return self.selectItemList.count 93 | } 94 | 95 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 96 | gLog.verbose("tableView=\(tableView), indexPath=\(indexPath)") 97 | 98 | let curCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: SelectItemTableViewCellId) 99 | 100 | curCell.selectionStyle = .None 101 | 102 | curCell.textLabel?.text = self.selectItemList[indexPath.row] 103 | curCell.textLabel?.textAlignment = .Left 104 | constrain(curCell.textLabel!){itemLabel in 105 | itemLabel.top == itemLabel.superview!.top 106 | itemLabel.bottom == itemLabel.superview!.bottom 107 | itemLabel.left == itemLabel.superview!.left + SelectTableViewPaddingX 108 | itemLabel.right == itemLabel.superview!.right - SelectTableViewPaddingX 109 | } 110 | 111 | if indexPath.row == self.curSelectIdx { 112 | setSelectedColor(curCell) 113 | } 114 | 115 | return curCell 116 | } 117 | 118 | func setSelectedColor(curCell:UITableViewCell) { 119 | curCell.contentView.backgroundColor = CommonButtonColor 120 | curCell.textLabel?.textColor = UIColor.whiteColor() 121 | } 122 | 123 | func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){ 124 | gLog.verbose("tableView=\(tableView), indexPath=\(indexPath)") 125 | 126 | self.curSelectIdx = indexPath.row 127 | self.returnSelectIdx = self.curSelectIdx 128 | gLog.verbose("self.curSelectIdx=\(self.curSelectIdx), self.returnSelectIdx=\(self.returnSelectIdx)") 129 | 130 | self.callCompletionHadlerAndHideVC() 131 | } 132 | 133 | func tableView(tableView: UITableView, didHighlightRowAtIndexPath indexPath: NSIndexPath) { 134 | gLog.verbose("tableView=\(tableView), indexPath=\(indexPath)") 135 | 136 | if let curCell = tableView.cellForRowAtIndexPath(indexPath) { 137 | setSelectedColor(curCell) 138 | } 139 | } 140 | 141 | /************************************************************************* 142 | * Current File Functions 143 | *************************************************************************/ 144 | 145 | func callCompletionHadlerAndHideVC(){ 146 | self.callCompletionHadler() 147 | 148 | dispatchMain_async({ 149 | self.dismissViewControllerAnimated(true, completion: nil) 150 | }) 151 | } 152 | 153 | func callCompletionHadler(){ 154 | gLog.verbose("self.curSelectIdx=\(self.curSelectIdx), self.returnSelectIdx=\(self.returnSelectIdx)") 155 | 156 | if self.completionHadler != nil { 157 | self.completionHadler!(self.returnSelectIdx) 158 | } 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /swift/UI/ViewController/TapToHideViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TapToHideViewController.swift 3 | // Xxx 4 | // 5 | // Created by licrifan on 16/6/19. 6 | // Copyright © 2016年 licrifan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /* 12 | click/tap the view out side of notHideView 13 | -> will dissmiss current view controller 14 | -> and call the hideVCHandler if not nil 15 | */ 16 | 17 | class TapToHideViewController: UIViewController, UIGestureRecognizerDelegate { 18 | var notHideView:UIView 19 | 20 | var hideVCHandler: ((Void) -> Void)? 21 | 22 | var tapToHideRecog:UITapGestureRecognizer! 23 | 24 | init(notHideView:UIView, hideVCHandler:((Void) -> Void)? = nil) { 25 | gLog.verbose("notHideView=\(notHideView)") 26 | 27 | self.notHideView = notHideView 28 | self.hideVCHandler = hideVCHandler 29 | 30 | super.init(nibName: nil, bundle: nil) 31 | } 32 | 33 | required init?(coder aDecoder: NSCoder) { 34 | fatalError("init(coder:) has not been implemented") 35 | } 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | 40 | self.tapToHideRecog = UITapGestureRecognizer(target: self, action: #selector(self.dismissCurVC(_:))) 41 | self.tapToHideRecog.numberOfTapsRequired = 1 42 | self.tapToHideRecog.delegate = self 43 | //self.tapToHideRecog.cancelsTouchesInView = false 44 | self.view.addGestureRecognizer(self.tapToHideRecog) 45 | } 46 | 47 | /************************************************************************* 48 | * Current File Functions 49 | *************************************************************************/ 50 | 51 | /*************************************************************************** 52 | * UIGestureRecognizerDelegate functions 53 | ***************************************************************************/ 54 | 55 | func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool { 56 | gLog.debug("touch=\(touch), touch.view=\(touch.view)") 57 | 58 | //get touch/tap point/position in current view 59 | let touchPoint = touch.locationInView(self.view) 60 | gLog.debug("touchPoint=\(touchPoint)") 61 | 62 | //check tap point in subview or not 63 | if CGRectContainsPoint(self.notHideView.frame, touchPoint){ 64 | gLog.verbose("clicked within notHideView=\(notHideView) -> should not hide") 65 | 66 | return false 67 | } else { 68 | gLog.verbose("clicked out of notHideView=\(notHideView) -> need hide") 69 | 70 | return true 71 | } 72 | } 73 | 74 | func dismissCurVC(tapRecog:UITapGestureRecognizer){ 75 | gLog.verbose("tapRecog=\(tapRecog)") 76 | 77 | gLog.verbose("hideVCHandler=\(hideVCHandler)") 78 | 79 | if self.hideVCHandler != nil { 80 | self.hideVCHandler!() 81 | } 82 | 83 | dispatchMain_async({ 84 | self.dismissViewControllerAnimated(true, completion: nil) 85 | }) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /swift/UI/ViewController/TapToHideViewController/TapToHideViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TapToHideViewController.swift 3 | // CrifanLibSwift 4 | // 5 | // Created by licrifan on 16/6/19. 6 | // Copyright © 2016年 licrifan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /* 12 | click/tap the view out side of notHideView 13 | -> will dissmiss current view controller 14 | -> and call the hideVCHandler if not nil 15 | */ 16 | 17 | class TapToHideViewController: UIViewController, UIGestureRecognizerDelegate { 18 | var notHideView:UIView 19 | 20 | var hideVCHandler: ((Void) -> Void)? 21 | 22 | var tapToHideRecog:UITapGestureRecognizer! 23 | 24 | init(notHideView:UIView, hideVCHandler:((Void) -> Void)? = nil) { 25 | gLog.verbose("notHideView=\(notHideView)") 26 | 27 | self.notHideView = notHideView 28 | self.hideVCHandler = hideVCHandler 29 | 30 | super.init(nibName: nil, bundle: nil) 31 | } 32 | 33 | required init?(coder aDecoder: NSCoder) { 34 | fatalError("init(coder:) has not been implemented") 35 | } 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | 40 | self.tapToHideRecog = UITapGestureRecognizer(target: self, action: #selector(self.dismissCurVC(_:))) 41 | self.tapToHideRecog.numberOfTapsRequired = 1 42 | self.tapToHideRecog.delegate = self 43 | //self.tapToHideRecog.cancelsTouchesInView = false 44 | self.view.addGestureRecognizer(self.tapToHideRecog) 45 | } 46 | 47 | /************************************************************************* 48 | * Current File Functions 49 | *************************************************************************/ 50 | 51 | /*************************************************************************** 52 | * UIGestureRecognizerDelegate functions 53 | ***************************************************************************/ 54 | 55 | func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool { 56 | gLog.debug("touch=\(touch), touch.view=\(touch.view)") 57 | 58 | //get touch/tap point/position in current view 59 | let touchPoint = touch.locationInView(self.view) 60 | gLog.debug("touchPoint=\(touchPoint)") 61 | 62 | //check tap point in subview or not 63 | if CGRectContainsPoint(self.notHideView.frame, touchPoint){ 64 | gLog.verbose("clicked within notHideView=\(notHideView) -> should not hide") 65 | 66 | return false 67 | } else { 68 | gLog.verbose("clicked out of notHideView=\(notHideView) -> need hide") 69 | 70 | return true 71 | } 72 | } 73 | 74 | func dismissCurVC(tapRecog:UITapGestureRecognizer){ 75 | gLog.verbose("tapRecog=\(tapRecog)") 76 | 77 | gLog.verbose("hideVCHandler=\(hideVCHandler)") 78 | 79 | if self.hideVCHandler != nil { 80 | self.hideVCHandler!() 81 | } 82 | 83 | dispatchMain_async({ 84 | self.dismissViewControllerAnimated(true, completion: nil) 85 | }) 86 | } 87 | } 88 | --------------------------------------------------------------------------------