顯示具有 Swift 標籤的文章。 顯示所有文章
顯示具有 Swift 標籤的文章。 顯示所有文章

dyld: lazy symbol binding failed: Symbol not found

最近針對安全性升級調整程式碼,並重新釋出自製的framework套件,結果安裝此套件的專案build得過,但遇到dyld: lazy symbol binding failed: Symbol not found錯誤無法run

最後參考此文章dyld: lazy symbol binding failed: Symbol not found - Swift Kingfisher

雖然不是因為使用Swift Kingfisher造成,但情況大同小異,最終在使用套件的專案之Podfile加上如下語法,並且主動執行一次pod install即可。這段語法用途在於把所有Pod專案的build setting中的Build Libraries for Distribution一次性的設定為YES。要手動設定也是可以。

post_install do |installer|

  installer.pods_project.targets.each do |target|

    target.build_configurations.each do |config|

      config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'

    end

  end

end

如果對語法有興趣,可以參考此文瞭解一下CocoaPods Hooks

Alamofire request在dispatchPrecondition發生錯誤

 實際發生錯誤的程式碼斷在Alamofire的Session.swift如下片段

extension Session: SessionStateProvider {

    func request(for task: URLSessionTask) -> Request? {

        dispatchPrecondition(condition: .onQueue(rootQueue)) // -->錯在此行

        return requestTaskMap[task]

    }

// 略

}

會出現此錯誤是因為App本身用了AF.request之外,又導入另一framework套件,而該套件也有使用Alamofire,使用的方式是自行建立一個新的Session如下,該套件透過自建的Session將AF.request替換成myAF.request執行各項API操作。

public let myAF: Session = {

    let configuration = URLSessionConfiguration.default

    configuration.requestCachePolicy = .reloadIgnoringCacheData

    configuration.tlsMinimumSupportedProtocol = .dtlsProtocol12

    let delegate = Session.default.delegate

    let manager = Session.init(configuration: configuration,

                               delegate: delegate,

                               startRequestsImmediately: true,

                               cachedResponseHandler: nil)

    return manager

}()


如果App本身未使用AF.request,只使用本套件,是沒有問題。但是當App有使用AF.request又導入本套件,就會因為多個執行緒造成錯誤,主要原因是因為Session的設計如下。

open class Session {

    /// Shared singleton instance used by all `AF.request` APIs. Cannot be modified.

    public static let `default` = Session()

}


因此,如果想要自訂configuration,又不想發生錯誤,套件內的configuration設定需要改成如下寫法

AF.session.configuration.requestCachePolicy = .reloadIgnoringCacheData

AF.session.configuration.tlsMinimumSupportedProtocol = .dtlsProtocol12

改完後,維持使用AF.request即可。

MessageKit如何刪除聊天訊息

MessageKit是市面上眾多Chat UI Library之一,比較特別的是每筆訊息都是透過UICollectionView的section代表,而非其他libray常用的UITableView的cell

如果要實現單獨刪除某一筆訊息,可以參考此文Delete message by long press on cell 

但我需要的功能又更進階,希望delte的action只能在部分聊天訊息發生作用,例如我自己傳送失敗的訊息才可以刪除。要達到此目的,只需修改collectionView(_ :canPerformAction:forItemAt:withSender) ~在判斷邏輯上增添自己設計的condition即可(如下圖粉紅色框起處)




SDWebImageDownloader cannot cache image

有在寫iOS專案的開發者絕對用過大名鼎鼎SDWebImage,絕大多數使用情境都是指定網路圖片url給某個UIImageView,以SDWebImage達到圖片快取的效果。

UIImageView載入網路圖片最簡單的方式透過sd_setImage方法直接取用,更進階的可以使用SDWebImageManager或SDWebImageDownloader,前者的用途兼具下載圖片與快取圖片,後者只能下載。

但有些圖片提供者要求必須在Http header指定安全性驗證用的value,這時候就只能使用無法圖片快取的SDWebImageDownloader。

為了解決這個問題,可以搭配SDImageCache使用,SDWebImageDownloader下載完後使用url作為key儲存至memory,下次再遇到相同url只要透過此key即可載入快取給UIImageView使用。

程式碼如下

if let imageCache = SDImageCache.shared.imageFromMemoryCache(forKey: myURLString) {

    imageView.image = imageCache

} else {

    SDWebImageDownloader.shared.setValue("myToken", forHTTPHeaderField: "headerName")

    SDWebImageDownloader.shared.downloadImage(with: myURLString) { (image, data, error, finished) in

        if image != nil && finished {

            SDImageCache.shared.storeImage(toMemory: image, forKey: myURLString)

            imageView.image = image

        }

        if error != nil {

            print("[下載圖片失敗] url:\(myURLString), error:\(error!)")

        }

    }

}

[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]限制UITextView的行數與換行規格

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

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

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

參考文章

[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 Podfile的From 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]使用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(伺服器資料)
                }
            }

        }