swift 在Swift中处理可变模型

https://www.swiftbysundell.com/posts/handling-mutable-models-in-swift

Observable
class Observable<Value> {
    private var value: Value
    private var observations = [UUID : (Value) -> Void]()

    init(value: Value) {
        self.value = value
    }

    func update(with value: Value) {
        self.value = value

        for observation in observations.values {
            observation(value)
        }
    }

    func addObserver<O: AnyObject>(_ observer: O,
                                   using closure: @escaping (O, Value) -> Void) {
        let id = UUID()

        observations[id] = { [weak self, weak observer] value in
            // If the observer has been deallocated, we can safely remove
            // the observation.
            guard let observer = observer else {
                self?.observations[id] = nil
                return
            }

            closure(observer, value)
        }

        // Directly call the observation closure with the
        // current value.
        closure(observer, value)
    }
}

swift Grdient按钮

现在,您可以在按钮上添加渐变,使用buttonautolayout更新

GradientButton

// MARK: - custom class 
class GradientButton: UIButton {
  override class var layerClass: Swift.AnyClass {
    return CAGradientLayer.self
  }
}


// MARK: - usage example of Gradient Button
guard let gradientLayer = button.layer as? CAGradientLayer else { return }
gradientLayer.colors = [UIColor.tangerine.cgColor, UIColor.marigold.cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)

swift 日期+扩展

dateextension
    ///checks the distance between two dates and returns true if there is no distance between them regarding the CalendarFormat
    func checkDateDistanceto(dateToCompare: Date, calendarFormat: CalendarFormat) -> Bool {
        var distance: Int = 1
        switch calendarFormat {
        case .day:
            guard let startDate = self.getFullHour(), let endDate = dateToCompare.getFullHour() else {
                return false
            }
            let dateComponents = (Calendar.current as NSCalendar).components(.hour, from: startDate, to: endDate, options: [])
            if let hour = dateComponents.hour {
                distance = hour
            } else {
                return false
            }
        case .week, .month:
            let dateComponents = (Calendar.current as NSCalendar).components(.day, from: self.startOfDay(), to: dateToCompare.startOfDay(), options: [])
            if let day = dateComponents.day {
                distance = day
            } else {
                return false
            }
        case .year:
            guard let startDate = self.startOfMonth(), let endDate = dateToCompare.startOfMonth() else {
                return false
            }
            let dateComponents = (Calendar.current as NSCalendar).components(.day, from: startDate, to: endDate, options: [])
            if let month = dateComponents.month {
                distance = month
            } else {
                return false
            }
        }
        if distance > 1 {
            return false
        } else {
            return true
        }
    }

swift 日期格式化程序

日期格式化程序

DateFormatter.swift
import Foundation
import XCTest

extension Date {

    static private func formatter(locale: String? = nil, timeZone: TimeZone? = nil) -> DateFormatter {

        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: locale ?? Locale.current.identifier)
        formatter.timeZone = timeZone ?? TimeZone.current

        return formatter
    }

    func toStringFormattedInterval(to date: Date, fromTemplate template: String, locale: String?) -> String {

        let formatter = DateIntervalFormatter()
        formatter.locale = Locale(identifier: locale ?? Locale.current.identifier)
        formatter.timeZone = TimeZone.current
        formatter.dateTemplate = template

        return formatter.string(from: self, to: date)
    }

    func toStringRelative(fromTemplate template:String, locale: String? = nil) -> String {

        let formatter = Date.formatter(locale: locale)
        formatter.dateFormat = DateFormatter.dateFormat(fromTemplate: template, options: 0, locale: formatter.locale)
        formatter.doesRelativeDateFormatting = true

        return formatter.string(from: self)
    }

    func toString(fromTemplate template:String, locale: String? = nil) -> String {

        let formatter = Date.formatter(locale: locale)
        formatter.dateFormat = DateFormatter.dateFormat(fromTemplate: template, options: 0, locale: formatter.locale)

        return formatter.string(from: self)
    }
}

enum Locales: String, CaseIterable {

    case us = "en-US"
    case gb = "en-GB"
    case de = "de-DE"
    case es = "es-ES"
    case mx = "es-MX"
    case fr = "fr-FR"
    case it = "it-IT"
    case nl = "nl-NL"
    case br = "pt-BR"
    case se = "sv-SE"
    case ru = "ru-RU"
    case ae = "ar-AE"
    case jp = "ja-JP"
    case kr = "ko-KR"
    case cn = "zh-CN"
}

class DateExtensionTests: XCTestCase {

    let date = Date(timeIntervalSince1970: 1554394188.6324)
    let template = "EEEEdMMM"

    func assertDate(fromTemplate template: String, locale: Locales, equalsTo: String) {

        XCTAssertEqual(date.toString(fromTemplate: template, locale: locale.rawValue), equalsTo)
    }

    func testDateWithWeekDayFormat() {

        assertDate(fromTemplate: template, locale: .us, equalsTo: "Thursday, Apr 4")
        assertDate(fromTemplate: template, locale: .gb, equalsTo: "Thursday 4 Apr")
        assertDate(fromTemplate: template, locale: .de, equalsTo: "Donnerstag, 4. Apr.")
        assertDate(fromTemplate: template, locale: .es, equalsTo: "jueves, 4 abr")
        assertDate(fromTemplate: template, locale: .mx, equalsTo: "jueves 4 de abr")
        assertDate(fromTemplate: template, locale: .fr, equalsTo: "jeudi 4 avr.")
        assertDate(fromTemplate: template, locale: .it, equalsTo: "giovedì 4 apr")
        assertDate(fromTemplate: template, locale: .nl, equalsTo: "donderdag 4 apr.")
        assertDate(fromTemplate: template, locale: .br, equalsTo: "quinta-feira, 4 de abr")
        assertDate(fromTemplate: template, locale: .se, equalsTo: "torsdag 4 apr.")
        assertDate(fromTemplate: template, locale: .ru, equalsTo: "четверг, 4 апр.")
        assertDate(fromTemplate: template, locale: .ae, equalsTo: "الخميس، 4 أبريل")
        assertDate(fromTemplate: template, locale: .jp, equalsTo: "4月4日 木曜日")
        assertDate(fromTemplate: template, locale: .kr, equalsTo: "4월 4일 목요일")
        assertDate(fromTemplate: template, locale: .cn, equalsTo: "4月4日 星期四")
    }
}

DateExtensionTests.defaultTestSuite.run()

swift 带触摸ID的钥匙串

keychain-with-touch-id.swift
    //  This two values identify the entry, together they become the
    //  primary key in the database
    let myAttrService = "app_name"
    let myAttrAccount = "first_name"

    // DELETE keychain item (if present from previous run)

    let delete_query: NSDictionary = [
        kSecClass: kSecClassGenericPassword,
        kSecAttrService: myAttrService,
        kSecAttrAccount: myAttrAccount,
        kSecReturnData: false
    ]
    let delete_status = SecItemDelete(delete_query)
    if delete_status == errSecSuccess {
        print("Deleted successfully.")
    } else if delete_status == errSecItemNotFound {
        print("Nothing to delete.")
    } else {
        print("DELETE Error: \(delete_status).")
    }

    // INSERT keychain item

    let valueData = "The Top Secret Message V1".data(using: .utf8)!
    let sacObject =
        SecAccessControlCreateWithFlags(kCFAllocatorDefault,
                                        kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
                                        .userPresence,
                                        nil)!

    let insert_query: NSDictionary = [
        kSecClass: kSecClassGenericPassword,
        kSecAttrAccessControl: sacObject,
        kSecValueData: valueData,
        kSecUseAuthenticationUI: kSecUseAuthenticationUIAllow,
        kSecAttrService: myAttrService,
        kSecAttrAccount: myAttrAccount
    ]
    let insert_status = SecItemAdd(insert_query as CFDictionary, nil)
    if insert_status == errSecSuccess {
        print("Inserted successfully.")
    } else {
        print("INSERT Error: \(insert_status).")
    }

    DispatchQueue.global().async {
        // RETRIEVE keychain item

        let select_query: NSDictionary = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrService: myAttrService,
            kSecAttrAccount: myAttrAccount,
            kSecReturnData: true,
            kSecUseOperationPrompt: "Authenticate to access secret message"
        ]
        var extractedData: CFTypeRef?
        let select_status = SecItemCopyMatching(select_query, &extractedData)
        if select_status == errSecSuccess {
            if let retrievedData = extractedData as? Data,
                let secretMessage = String(data: retrievedData, encoding: .utf8) {

                print("Secret message: \(secretMessage)")

                // UI updates must be dispatched back to the main thread.

                DispatchQueue.main.async {
                    self.messageLabel.text = secretMessage
                }

            } else {
                print("Invalid data")
            }
        } else if select_status == errSecUserCanceled {
            print("User canceled the operation.")
        } else {
            print("SELECT Error: \(select_status).")
        }
    }

swift SeparateCode

SeparateCode
//  ==============================================================================
//  Internal Methods
//  ==============================================================================
extension {

}

swift 此函数创建一个目录。

#此url扩展创建一个具有给定名称的目录,或者如果目录存在则返回用户给出的名称的url,如果无法创建目录,则返回nil

CreateDirectory.swift
import Foundation

extension URL {
    static func createFolder(folderName: String) -> URL? {
        let fileManager = FileManager.default
        // Get document directory for device, this should succeed
        if let documentDirectory = fileManager.urls(for: .documentDirectory,
                                                    in: .userDomainMask).first {
            // Construct a URL with desired folder name
            let folderURL = documentDirectory.appendingPathComponent(folderName)
            // If folder URL does not exist, create it
            if !fileManager.fileExists(atPath: folderURL.path) {
                do {
                    // Attempt to create folder
                    try fileManager.createDirectory(atPath: folderURL.path,
                                                    withIntermediateDirectories: true,
                                                    attributes: nil)
                } catch {
                    // Creation failed. Print error & return nil
                    print(error.localizedDescription)
                    return nil
                }
            }
            // Folder either exists, or was created. Return URL
            return folderURL
        }
        // Will only be called if document directory not found
        return nil
    }
}

swift 错误处理

ErrorHandling
import UIKit

class ViewController: UIViewController {
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        do {
            try makeBroth(numberOfCooks: 100)
        } catch let error as LocalizedError {
            let title = error.errorDescription
            let message = [
                error.failureReason,
                error.recoverySuggestion
            ].compactMap { $0 }
             .joined(separator: "\n\n")

            let alertController =
                UIAlertController(title: title,
                                  message: message,
                                  preferredStyle: .alert)
            alertController.addAction(
                UIAlertAction(title: "OK",
                              style: .default)
            )

            self.present(alertController, animated: true, completion: nil)
        } catch {
            // handle other errors...
        }
    }
}


extension UIAlertController {
  convenience init<Error>(_ error: Error,
                          preferredStyle: UIAlertController.Style)
    where Error: LocalizedError
  {
    let title = error.errorDescription
    let message = [
        error.failureReason,
        error.recoverySuggestion
    ].compactMap { $0 }
     .joined(separator: "\n\n")

    self.init(title: title,
                message: message,
                preferredStyle: .alert)
  }
}

swift 带有日期选择器的UITextField

uitextfield-with-date-picker.swift
// Inizializza il selettore della data di nascita
dateBirthDatePicker = UIDatePicker()
dateBirthDatePicker?.datePickerMode = .date
txtDateBirth.inputView = dateBirthDatePicker
let dateBirthAccessory = UIToolbar()
dateBirthAccessory.sizeToFit()
dateBirthAccessory.items = [ UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(onDateBirthDateBtnDoneTapped)) ]
txtDateBirth.inputAccessoryView = dateBirthAccessory

swift Swift可解码多个自定义日期

Swift可解码多个自定义日期

RSSFeed.swift
import Foundation

extension DateFormatter {
    static let iso8601Full: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
        formatter.calendar = Calendar(identifier: .iso8601)
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        formatter.locale = Locale(identifier: "en_US_POSIX")
        return formatter
    }()
    
    static let yyyyMMdd: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        formatter.calendar = Calendar(identifier: .iso8601)
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        formatter.locale = Locale(identifier: "en_US_POSIX")
        return formatter
    }()
}

public struct RSSFeed: Codable {
    public struct Feed: Codable {
        public struct Podcast: Codable {
            public let name: String
            public let artistName: String
            public let url: URL
            public let releaseDate: Date
        }
        
        public let title: String
        public let country: String
        public let updated: Date
        public let podcasts: [Podcast]
        
        private enum CodingKeys: String, CodingKey {
            case title
            case country
            case updated
            case podcasts = "results"
        }
    }
    
    public let feed: Feed
}

public typealias Feed = RSSFeed.Feed
public typealias Podcast = Feed.Podcast

extension Podcast {
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        artistName = try container.decode(String.self, forKey: .artistName)
        url = try container.decode(URL.self, forKey: .url)
        let dateString = try container.decode(String.self, forKey: .releaseDate)
        let formatter = DateFormatter.yyyyMMdd
        if let date = formatter.date(from: dateString) {
            releaseDate = date
        } else {
            throw DecodingError.dataCorruptedError(forKey: .releaseDate, in: container, debugDescription: "Date string does not match format expected by formatter.")
        }
    }
}

let json = """
{
"feed": {
"title":"Top Audio Podcasts",
"country":"gb",
"updated":"2017-11-16T02:02:55.000-08:00",
"results":[
{
"artistName":"BBC Radio",
"name":"Blue Planet II: The Podcast",
"releaseDate":"2017-11-12",
"url":"https://itunes.apple.com/gb/podcast/blue-planet-ii-the-podcast/id1296222557?mt=2"
},
{
"artistName":"Audible",
"name":"The Butterfly Effect with Jon Ronson",
"releaseDate":"2017-11-03",
"url":"https://itunes.apple.com/gb/podcast/the-butterfly-effect-with-jon-ronson/id1258779354?mt=2"
},
{
"artistName":"TED",
"name":"TED Talks Daily",
"releaseDate":"2017-11-16",
"url":"https://itunes.apple.com/gb/podcast/ted-talks-daily/id160904630?mt=2"
}
]
}
}
"""

let data = Data(json.utf8)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601Full)
let rssFeed = try! decoder.decode(RSSFeed.self, from: data)

let feed = rssFeed.feed
print(feed.title, feed.country, feed.updated)

feed.podcasts.forEach {
    print($0.name)
}