実用的なString Extension

SwiftのStringは色々な考慮上、便利なSubstring方法を提供されていない。LeetCodeをやる時Stringの問題が合うとかなり面倒くさい。下記のextensionをコピペーすれば大変助かる。

extension StringProtocol {
    subscript(_ offset: Int)                     -> Element     { self[index(startIndex, offsetBy: offset)] }
    subscript(_ range: Range<Int>)               -> SubSequence { prefix(range.lowerBound+range.count).suffix(range.count) }
    subscript(_ range: ClosedRange<Int>)         -> SubSequence { prefix(range.lowerBound+range.count).suffix(range.count) }
    subscript(_ range: PartialRangeThrough<Int>) -> SubSequence { prefix(range.upperBound.advanced(by: 1)) }
    subscript(_ range: PartialRangeUpTo<Int>)    -> SubSequence { prefix(range.upperBound) }
    subscript(_ range: PartialRangeFrom<Int>)    -> SubSequence { suffix(Swift.max(0, count-range.lowerBound)) }
}

参考:https://stackoverflow.com/questions/24092884/

Dynamic Font無効化

Apple DocumentからdynamicTypeSize(_:)という関数を発見した。
下記の例はフォントタイプを強制的にxLargeに設定する:

ContentView()
    .dynamicTypeSize(.xLarge)

下記の例はフォントタイプをlarge以下限定する:

ContentView()
    .dynamicTypeSize(...DynamicTypeSize.large)

Appleの本意はプレビューに使わせるが、フォントサイズ大きすぎるとUI崩壊の原因になる。フォントタイプをある範囲内限定してもいいじゃないかと私が思った。
しかしこれはiOS 15+の関数なので、とりあえず待ちましょう。

実用的なBundle Extension for Jsonファイルロード

extension Bundle {
    func load<T: Codable>(from fileName: String) -> T? {
        guard let url = url(forResource: fileName, withExtension: "json") else { return nil }

        guard let data = try? Data(contentsOf: url) else {
            print("File \(fileName) does not exist.")
            return nil
        }
        
        let decoder = JSONDecoder()
        guard let loaded = try? decoder.decode(T.self, from: data) else {
            print("Failed to decode \(fileName).")
            return nil
        }
        
        print("loaded \(fileName)")
        return loaded
    }
}

実用的なFileManager Extension for Jsonファイルのセーブ/ロード

enum FileName: String {
    case pc
    case gameData
}

extension FileManager {
    func load<T: Codable>(from fileName: FileName) -> T? {
        let url = getDocumentsDirectory().appendingPathComponent(fileName.rawValue)

        guard let data = try? Data(contentsOf: url) else {
            print("File \(fileName.rawValue) does not exist.")
            return nil
        }
        
        let decoder = JSONDecoder()
        guard let loaded = try? decoder.decode(T.self, from: data) else {
            print("Failed to decode \(fileName.rawValue).")
            return nil
        }
        
        print("loaded \(fileName.rawValue)")
        return loaded
    }
    
    func save<T: Codable>(_ t: T, to fileName: FileName) -> Error? {
        let encoder = JSONEncoder()
        let url = getDocumentsDirectory().appendingPathComponent(fileName.rawValue)
        
        do {
            let encoded = try encoder.encode(t)
            try encoded.write(to: url, options: [.atomicWrite, .completeFileProtection])
        } catch {
            return error
        }
        
        return nil
    }
    
    private func getDocumentsDirectory() -> URL {
        let paths = self.urls(for: .documentDirectory, in: .userDomainMask)
        return paths[0]
    }
}

Optionalプロパティのdecode方法

例えばプロパティvar weapon: Weapon?をdecodeしよう場合、decodeIfPresentは便利です。書き方は下記です:

weapon = try container.decodeIfPresent(Weapon.self, forKey: .weapon)

関連記事:

cecil-it.hatenadiary.com