[iOS]App背景設定滿版圖片

App多數都會在歡迎頁塞個全版的美美背景圖,一般來說做法可以參考這篇如何讓 iOS App 畫面擁有背景圖片

但如果你想要一款背景圖打死所有尺寸,又不希望圖片被拉伸變形,可以在viewDidLoad這樣做

Objective C
self.view.layer.contents = (id)[UIImage imageNamed:@"背景圖檔案名稱"].CGImage;

Swift
self.view.layer.contents = UIImage.init(named:"背景圖檔案名稱")!.cgImage

[iOS]SourceTree error : 'yourXXX.git' does not appear to be a git repository

一個很久沒動的專案,簽入異動至server前,在pull時遭遇如下錯誤
Xcode






Source Tree

後來發現是Remote repository paths不知為何跑掉,參考下圖修改即可

[iOS]限制UITextView的行數與換行規格

UITextView不像UILabel有文字行數、文字斷行規則的屬性可以直接設定,要再往下一層textContainer才能找到相對應的屬性,寫法如下

Swift
textView.textContainer.maximumNumberOfLines = 1
textView.textContainer.lineBreakMode = .byTruncatingTail

Objective C
textView.textContainer.maximumNumberOfLines = 1;
textView.textContainer.lineBreakMode = NSLineBreakByTruncatingTail;

參考文章

[iOS]UIDocumentPickerViewController支援docx, pptx, xlsx格式

依據Apple Document說明,Microsoft文件的UTI如下
doc com.microsoft.word.doc
ppt com.microsoft.powerpoint.ppt
xls com.microsoft.excel.xls

但沒有提到docx, pptx, xlsx格式,後來翻到一篇實用的文章有列出此三款UTI如下
docx org.openxmlformats.wordprocessingml.document
pptx org.openxmlformats.presentationml.presentation
xlsx   org.openxmlformats.spreadsheetml.sheet

經實際測試,不需要設置info.plist,直接使用UTI即可
sample code如下:
NSArray *documentTypes = @[@"com.microsoft.word.doc", @"org.openxmlformats.wordprocessingml.document", @"com.microsoft.excel.xls", @"org.openxmlformats.spreadsheetml.sheet", @"com.microsoft.powerpoint.ppt", @"org.openxmlformats.presentationml.presentation"];
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:documentTypes inMode:UIDocumentPickerModeOpen];
documentPicker.delegate = self;
documentPicker.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:documentPicker animated:YES completion:nil];

[iOS]stringByAddingPercentEscapesUsingEncoding vs stringByAddingPercentEncodingWithAllowedCharacters

'stringByAddingPercentEscapesUsingEncoding:' is deprecated: first deprecated in iOS 9.0 - Use -stringByAddingPercentEncodingWithAllowedCharacters: instead, which always uses the recommended UTF-8 encoding, and which encodes for a specific URL component or subcomponent since each URL component or subcomponent has different rules for what characters are valid.

如上述文字,不要再用過期的stringByAddingPercentEscapesUsingEncoding,請愛用stringByAddingPercentEncodingWithAllowedCharacters

但stringByAddingPercentEncodingWithAllowedCharacters還要指定你要用哪一款Character Sets,像我的網址如下
http://myhost/User/karen_chang@quantatw.com.png?ModifyDate=2018-06-14T15:30:33.819000
我不希望query string後面的string被解譯錯誤,那就要選用URLQueryAllowedCharacterSet

其他五款的選用時機,請參考Apple Document
不要看網路上都用URLHostAllowedCharacterSet就亂抄,請實際依你的網址選用適合的

[iOS]設定UIImage的透明度

平常都是透過UIImageView設定背景色的透明度,但如果要圖片本身帶有透明度效果,可參考How to set the opacity/alpha of a UIImage?這篇文章的作法,參考Nick H247的做法修改如下:

- (UIImage *)imageByApplyingAlpha:(UIImageView *)imageView alpha:(CGFloat) alpha {
    UIGraphicsBeginImageContextWithOptions(imageView.frame.size, NO, 0.0f);
    
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGRect area = CGRectMake(0, 0, imageView.frame.size.width, imageView.frame.size.height);
    
    CGContextScaleCTM(ctx, 1, -1);
    CGContextTranslateCTM(ctx, 0, -area.size.height);
    
    //CGContextSetBlendMode(ctx, kCGBlendModeMultiply);
    
    CGContextSetAlpha(ctx, alpha);
    
    CGContextDrawImage(ctx, area, imageView.image.CGImage);
    
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    
    UIGraphicsEndImageContext();
    
    return newImage;
}

[iOS]隱藏UITableView的section header

最近產品畫面用了Grouped UITableView,但卻不想要每個Secton Header都出現,想要永遠隱藏第一個Section header,其他都出現。

照理說
tableView:viewForHeaderInSection 方法 return nil
heightForHeaderInSection 方法 return 0
就可以了

但怎麼試都還是在畫面上看到第一個section Header,後來發現heightForHeaderInSection至少要設一個大於零的數字就好,例如設一個超小的0.1 之類的

[iOS]包裝Swift framework

找到一篇寫得極好的文章(文章1),包出來的framework可以一併套用在模擬器&實機!!
但照著做後,發現自己寫的framework中有引用第三方元件的部分都有問題,無法找到所使用的module(換言之就是import失敗),後來改了Valid Architecture,加上x86_64即可,至於armv7或x86_64這些配置值的定義,可以再參考文章2

文章1
Swift Framework — Ch: 1 . Develop a Swift Framework
文章2
iOS armv7, armv7s, arm64区别与应用32位、64位配置

[iOS]包含closure為參數的function 如何由Objective-C改寫為Swift

記錄一下,如果Objective-C有個方法如下宣告
-(void)POST:(NSString *)userId withCrashTitle:(NSString *)title andContent:(NSString *)content success:(void (^)(BOOL))success failure:(void (^)(NSError *))failure {
}

若改寫為Swift如下
func mailErrorLog(userId:String, withCrashTitle:String, andContent:String, success:(Bool) -> Void, failure:(Error) -> Void) -> Void {
}

參考文章 https://hugolu.gitbooks.io/learn-swift/content/Basic/Closure.html

CocoaPods could not find compatible versions for pod

最近開始用Swift 4開發,想當然third party也要用到最新,以Alamofire、PKHUD為例,我分別需要4.7版、5.0版

        根據Alamofire官方PKHUD官方pod file應該是
        pod 'Alamofire', '~>  4.7'
        pod 'PKHUD', '~> 5.0'










結果pod install會遇到如下錯誤
        [!] CocoaPods could not find compatible versions for pod "Alamofire":
        In Podfile:
           Alamofire (~> 4.7)
        None of your spec sources contain a spec satisfying the dependency: `Alamofire (~> 4.7)`.
















實際上用pod search也的確是找不到Alamofire 4.7版、PKHUD 5.0版,解決方式參考CocoaPods Guides - The PodfileFrom a podspec in the root of a library repo.
修正後的pod file為
        pod 'Alamofire', :git => 'https://github.com/Alamofire/Alamofire.git', :tag => '4.7.0'
        pod 'PKHUD', :git => 'https://github.com/pkluz/PKHUD.git', :branch => 'release/swift4'
   

再pod install成功執行如下
    

[iOS]Alamofire download image

承前篇Alamofire GET request
下載圖片也大同小異,非常簡單~直接把response回來的data轉給UIImageView使用即可

Alamofire.request(logoUrl).responseData(completionHandler: { (response) in               
       switch response.result {
       case .success(let data):
              self.myImageView.image = UIImage(data: data)
       case .failure(let error):
              print(error)                   
       }               
})

[iOS]Swift 4擷取字串

subString在Swift 4被無情的deprecated嘞XD  紀錄一下擷取串的寫法

假設有一個圖片網址logoUrl,下載前我想用該網址提供的檔案名稱A123.png來做後續的存檔檔名,需要將A123.png擷取出來,寫法如下

        let logoUrl = "http://domain/XXX/Company/A123.png"
        let range = logoUrl.range(of: "/", options: .backwards)
        let index = logoUrl.index(after: (range?.lowerBound)!)
     
        let filename1 = String(logoUrl[index...])
        let filename2 = logoUrl.suffix(from: index)
        print(filename1)  // 成功擷取出A123.png
        print(filename2)  // 成功擷取出A123.png

[iOS]判斷字串是否有中文

紀錄一下中文的比對是\u4e00-\u9fa5

-(BOOL)isChineseCharacter:(NSString*)searchText {
    searchText = [searchText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
    NSString *searchRegex = @"[\u4e00-\u9fa5]";
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", searchRegex];
    BOOL matches = [predicate evaluateWithObject:searchText];
    return matches;
}

參考資料
[iOS] 正規表達式 (Regular Expression)
Objective-C笔记之正则表达式

[iOS]使用auth0的JWT元件

不會使用CocoaPods或Carthage者,可在GitHub下載專案後,加入下圖紅色框內檔案即可使用

簡易的decode程式碼範例
    func testJWT() -> Void {
        let testToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3NhbXBsZXMuYXV0aDAuY29tIiwic3ViIjoiYXV0aDB8MTAxMDEwMTAxMCIsImF1ZCI6Imh0dHBzOi8vc2FtcGxlcy5hdXRoMC5jb20iLCJleHAiOjEzNzI2NzQzMzYsImlhdCI6MTM3MjYzODMzNiwianRpIjoicXdlcnR5MTIzNDU2IiwibmJmIjoxMzcyNjM4MzM2fQ.LvF9wSheCB5xarpydmurWgi9NOZkdES5AbNb_UWk9Ew"
        do {
            let jwt = try decode(jwt: testToken)
            print("JWT raw data: \(jwt)")
        } catch let error as NSError {
            print("JWT error: \(error.localizedDescription)")
        }
    }

decode後印出來的字串可以至jwt.io網站驗證是否正確

[Reprinted]isKindOfClass in Swift

Stack Overflow轉載有三種方式,在Swift 4也是不變
Option 1:
if view is UITabBarItem {

}

Option 2:
if view.isKind(of:UITabBarItem.self) {

}

Option 3:
if view.isMember(of:UITabBarItem.self) {

}

The difference between isKind:of and isMember:of is:
●isKind:of returns YES if the receiver is an instance of the exact class, OR if the receiver is an instance of any subclass of the class.
●isMember:of only returns YES if the receiver is an instance of the exact class you're checking against

[iOS]Alamofire GET request

紀錄一下等同於Objective C神級AFNetworking的Alamofire

request&response都為JSON format時,GET request使用方式如下範例

        let url = "你的API網址"
        let parameters: Parameters = ["CompanyId": "AAAA"]
        let headers: HTTPHeaders = [
            "客製的header": "BBBB",
            "Accept": "application/json"
        ]

        Alamofire.request(url, method: HTTPMethod.get, parameters: parameters, encoding:URLEncoding.default , headers: headers).responseJSON { response in
            if let error = response.result.error {
                print("GetCompanyInformation error: \(String(describing: error))")
            } else {
                print("Request: \(String(describing: response.request))")   // original url request(原始的URL要求)
                print("Response: \(String(describing: response.response))") // http url response(URL回應)
                print("Result: \(response.result)")                         // response serialization result(回應的序列化結果)
             
                if let json = response.result.value {
                    print("JSON: \(json)") // serialized json response
                }
             
                if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
                    print("Data: \(utf8Text)") // original server data as UTF8 string(伺服器資料)
                }
            }

        }

[iOS] ld: warning: directory not found for option

遇到ld: warning: directory not found for option通常是因為參考的檔案遺失,但最近遇到一個較特殊的狀況,單單只是把專案中的(實體)資料夾改名,就報錯....以我的例子是把資料夾名稱由framework改為Frameworks

解決方式是到專案的「Build Settings→Search Paths→Framework Search Paths」,將不存在的路徑刪除即可

[iOS]hide StatusBar programmatically

以程式動態隱藏iOS StatusBar

BOOL conditionIsTrue;

-(void) someEventTriggerTheCondition:(BOOL)enable {
    // 某些條件成立...
    self.conditionIsTrue = enable;

    [UIView animateWithDuration:0.25 animations:^{
        [self setNeedsStatusBarAppearanceUpdate];
    }];
}

// 此方法請參考Apple文件
- (BOOL)prefersStatusBarHidden {
    return keyboardShow;
}

[iOS]取代NSAttributedString中的部分字串

網路上都是麻煩的方式,後來找到一個最適用的,紀錄如下
How to replace occurrences of a pattern with a NSAttributedString
這個方法可以避免修改到原始的attribute,只純粹把不要的substring弄掉

[iOS]Enbable alert action depending on UITextField in UIAlertController

UIAlertController是iOS中很常用的互動方式,有時候跳出的對話框會需要用戶填寫一些資訊再送出,而非單純YES/NO

若想做的比較合理,當然是要進一步判斷用戶是否確實填寫UITextField,沒填就鎖住(disable)UIAlertController的按鈕,有填才啟用(enable)按鈕
      沒輸入文字 按鈕disable           有輸入文字 按鈕enable


UIAlertController設計如下
UIAlertController * alert = [UIAlertController
                             alertControllerWithTitle:@"命名此對話"
                             message:@"請輸入文字為此對話命名"
                             preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
    textField.text = self.groupName;
    textField.placeholder = @"群組名稱";
    [textField addTarget:self action:@selector(groupNameChanged:) forControlEvents:UIControlEventEditingChanged];
}];
UIAlertAction* cancel = [UIAlertAction
                         actionWithTitle:@"取消"
                         style:UIAlertActionStyleDefault
                         handler:^(UIAlertAction * action) {
                             [alert dismissViewControllerAnimated:YES completion:nil];
                         }];
UIAlertAction* ok = [UIAlertAction
                     actionWithTitle:@"確認"
                     style:UIAlertActionStyleDefault
                     handler:^(UIAlertAction * action) {
                         UITextField *textField = alert.textFields.firstObject;
                         // [略]取textField.text作後續使用
                         [alert dismissViewControllerAnimated:YES completion:nil];
                     }];
[alert addAction:cancel];
[alert addAction:ok];

[self presentViewController:alert animated:YES completion:^{
    [alert.textFields[0] becomeFirstResponder];
}];

依據文字輸入與否, 動態決定按鈕狀態的selector method
-(void)groupNameChanged:(UITextField *)textField {
    UIResponder *responder = textField;
    Class uiacClass = [UIAlertController class];
    while (![responder isKindOfClass: uiacClass]) {
        responder = [responder nextResponder];
    }
    UIAlertController *alert = (UIAlertController*) responder;
    UIAlertAction *okAction  = [alert.actions objectAtIndex:1];
    NSString *inputString = textField.text;
    inputString = [inputString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
    if(inputString.length == 0)
        [okAction setEnabled:NO];
    else
        [okAction setEnabled:YES];
}

[iOS]this class is not key value coding-compliant for the key

在xib增加新的元件,並透過IBOutlet去連結h檔,會一直報錯
this class is not key value coding-compliant for the key

後來發現是連結時,Object選項要主動選擇對應的h檔檔名,不然預設會是File's Owner就會報錯,之前開發都不會遇到這問題,不知是否為Xcode的bug捏(✘﹏✘ა)

正確的設定圖


錯誤的設定圖