最近文章

Swift在 iOS 13上获取Wi-Fi SSID(服务集标识)

Apple在iOS 13进行了很多更改,以保护使用iOS用户的隐私。其中包括了网络上的更改,这样会导致我们的应用程序有些服务不能使用。CNCopyCurrentNetworkInfo是用来获取Wi-Fi信息的。可以通过下面示例的方法获取Wi-Fi的SSID:import Foundationimport SystemConfiguration.CaptiveNetworkfunc getWiFiS
标签:

Swift 3从异步调用返回数据

是不能直接在异步调用返回数据,一种替代的方案是向异步调用的函数里传入回调函数,当异步任务完成后,使用回调函数处理结果。Swift 3示例:class func getData(completionHandler: @escaping (data: NSArray) -> ()) {...let task = session.dataTask(with:url) { data, resp
标签:

Swift 3/4获取AppDelegate实例的引用

Swift 3/4获取AppDelegate实例:func appDelegate() -> AppDelegate{ return UIApplication.shared.delegate as! AppDelegate}对于Swift < 3func appDelegate() -> AppDelegate{ return UIApplication.share
标签:

Swift禁止UITableView选中高亮

禁止UITableView选中高亮,可以对UITableViewCell实例设置样式:Swift 2cell.selectionStyle = UITableViewCellSelectionStyle.None Swift 3cell.selectionStyle = .none
标签:

Swift 3/4缩放UIImage的方法

给UIImage添加extension(兼容Swift3 和Swift 4)extension UIImage {     func scaled(withSize size: CGSize) -> UIImage {   &nbs
标签:

Swift修改UISearchBar上取消按钮文本的颜色

Swift 2,3,4在语法上少有不同,以下是各个Swift版本修改取消按钮文本颜色的代码片段Swift 4.0let cancelButtonAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white]  UIBarButtonItem.appearan
标签:

查看项目使用Swift的版本

使用xcode创建swift项目,你需要知道在项目中正在使用的swift版本。使用终端查看:swift -version 比较稳妥的方法是在xcode上查看,因为每个项目可能设定的swift 版本不一样。按路径打开:Project -> 选择项目的target -> Build Settings ->
标签:

Xcode 9.2上传app报错:ERROR ITMS-90534: "Invalid Toolchain."

新安装Xcode 9.2 (9C40b),上传app到App Store时报错:ERROR ITMS-90534: "Invalid Toolchain. New apps and app updates must be built wit
标签:

Swift日期和字符串互相转换(Swift 3/Swift 4)

Swift随着版本的升级,日期类都会有一点变化。所以各个版本的Swift日期和字符串的转换有所不同。Swift 4String转换为Datevar dateString = "2017-11-17" var dateFormatter = DateFormatter() // dateFormat
标签:

处理Swift 4 使用#selector警告:Argument of '#selector' refers to instance method that depends on '@objc' attribute inference deprecated in Swift 4

Swift 3升级到Swift 4,在使用#selector可能会报以下警告:Argument of '#selector' refers to instance method 'doAction()' in 'ViewController' that depends&
标签:

Swift3.1检测网络连接状态(包含WiFi以及4G)

Reachability.swiftimport Foundation import SystemConfiguration class Reachability {     var hostname: String?     var isRunn
标签:

Xcode 9缩放iPhone模拟器快捷键

Xcode 9之后新增了拖拽的方式缩放iPhone模拟器。Xcode 9.1界面操作Menubar >> Window >> Physical Size 或 Pixel Accurate缩放快捷键⌘ 1:Physical Size,按物理尺寸缩放 
标签:

下载安装iOS 9.1 模拟器失败:The certificate for this server is invalid

问题下载iOS 9.1模拟器失败,报错信息:Could not download and install iOS 9.1 Simulator. The certificate for this server is invalid. You&
标签:

Swift在 iOS 13上获取Wi-Fi SSID(服务集标识)

Apple在iOS 13进行了很多更改,以保护使用iOS用户的隐私。其中包括了网络上的更改,这样会导致我们的应用程序有些服务不能使用。

CNCopyCurrentNetworkInfo是用来获取Wi-Fi信息的。可以通过下面示例的方法获取Wi-Fi的SSID:

import Foundation
import SystemConfiguration.CaptiveNetwork
func getWiFiSsid() -> String? {
var ssid: String?
if let interfaces = CNCopySupportedInterfaces() as NSArray? {
for interface in interfaces {
if let interfaceInfo = CNCopyCurrentNetworkInfo(interface as! CFString) as NSDictionary? {
ssid = interfaceInfo[kCNNetworkInfoKeySSID as String] as? String
break
}
}
}
return ssid
}

在iOS 13之前,我们只要能够连接上wifi,可以获取wifi信息,我们就可以活到已连接的Wi-Fi SSID。

而从iOS 13开始,只有在下面三种情况下可以,获取WIFI的SSID:

  • 应用被授权获取位置信息
  • 应用有启动VPN的配置
  • 如果是使用NEHotspotConfiguration的网络应用,则需要苹果的额外批准

如果不是这三种情况,iOS 13以上的版本,CNCopyCurrentNetworkInfo就会返回nil。这样应用使用了 Wi-Fi SSID的功能就不能使用了。

Swift 3从异步调用返回数据

是不能直接在异步调用返回数据,一种替代的方案是向异步调用的函数里传入回调函数,当异步任务完成后,使用回调函数处理结果。

Swift 3示例:

class func getData(completionHandler: @escaping (data: NSArray) -> ()) {
...
let task = session.dataTask(with:url) {
data, response, error in
...
resultsArray = results
completionHandler(data: resultsArray)
}
...
task.resume()
}

使用:

override func viewDidLoad() {
User.getData {
data in
println("View Controller: \(data)")
}
}

Swift 3/4获取AppDelegate实例的引用

Swift 3/4获取AppDelegate实例:

func appDelegate() -> AppDelegate{
return UIApplication.shared.delegate as! AppDelegate
}

对于Swift < 3

func appDelegate() -> AppDelegate{
return UIApplication.sharedApplication().delegate as! AppDelegate
}

Swift禁止UITableView选中高亮

禁止UITableView选中高亮,可以对UITableViewCell实例设置样式:

Swift 2

cell.selectionStyle = UITableViewCellSelectionStyle.None

Swift 3

cell.selectionStyle = .none

Swift 3/4缩放UIImage的方法

给UIImage添加extension(兼容Swift3 和Swift 4)

extension UIImage {

    func scaled(withSize size: CGSize) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
        defer { UIGraphicsEndImageContext() }
        draw(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
        return UIGraphicsGetImageFromCurrentImageContext()!
    }
}

scaled()接收指定的尺寸缩放UIImage。

如果要按纵横比缩放到给定尺寸,可以添加以下extension:

extension UIImage {

    func scaleToFitSize(size: CGSize) -> UIImage {
        let aspect = self.size.width / self.size.height
        if size.width / aspect <= size.height {
            return scaledImage(withSize: CGSize(width: size.width, height: size.width / aspect))
        } else {
            return scaledImage(withSize: CGSize(width: size.height * aspect, height: size.height))
        }
    }

}

使用:

let image = UIImage(named: "apple")
let scaledImage = image.scaleToFitSize(size: CGSize(width: 50.0, height: 50.0))

Swift修改UISearchBar上取消按钮文本的颜色

Swift 2,3,4在语法上少有不同,以下是各个Swift版本修改取消按钮文本颜色的代码片段

Swift 4.0

let cancelButtonAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white]
 UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes , for: .normal)

Swift 3.1

let cancelButtonAttributes: [String: AnyObject] = [NSForegroundColorAttributeName: UIColor.white]
UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)

Swfit 2

let cancelButtonAttributes: NSDictionary = [NSForegroundColorAttributeName: UIColor.whiteColor()]
UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes as? [String : AnyObject], forState: UIControlState.Normal)

查看项目使用Swift的版本

使用xcode创建swift项目,你需要知道在项目中正在使用的swift版本。

使用终端查看:

swift -version

比较稳妥的方法是在xcode上查看,因为每个项目可能设定的swift 版本不一样。

按路径打开:

Project -> 选择项目的target -> Build Settings -> Swift Compiler Language -> Swift Language Version -> 在下拉列表选择对应的语言版本

其中在Build Setting,可以在搜索框输入swift来说搜索Swift Compiler Language,操作如图:

Xcode 9.2上传app报错:ERROR ITMS-90534: "Invalid Toolchain."

新安装Xcode 9.2 (9C40b),上传app到App Store时报错:

ERROR ITMS-90534: "Invalid Toolchain. New apps and app updates must be built with the public (GM) versions of Xcode 6 or later, macOS, and iOS SDK or later. Don't submit apps built with beta software including beta macOS builds."

这个可能是Xcode 9.2的bug。苹果的开发者论坛Xcode 9.2 app uploading broken?给出了一个可能的解决方案:

Right-Click on the archive file (.xcarchive) > Show Package Contents > Products > Applications > Right-Click on the app file > Show Package Contents > Double-Click on Info.plist to edit it
Change value of DTXcodeBuild from 9C40b to 9C40

主要就是展开打包文件.xcarchive,找到app的Info.plist,把DTXcodeBuild的9C40b改为9C40

Swift日期和字符串互相转换(Swift 3/Swift 4)

Swift随着版本的升级,日期类都会有一点变化。所以各个版本的Swift日期和字符串的转换有所不同。

Swift 4

String转换为Date

var dateString = "2017-11-17"
var dateFormatter = DateFormatter()

// dateFormat需要和输入的字符串相匹配,否则返回nil
dateFormatter.dateFormat = "yyyy-MM-dd"

//`date(from:)` 返回的是可选类型 
var dateFromString: Date? = dateFormatter.date(from: dateString)

Date转换为String

var formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let date = Date()
let stringDate: String = formatter.string(from: date)

Swift 3

String to NSDate

var dateString = "2017-11-17"
var dateFormatter = DateFormatter()

// dateFormat需要和输入的字符串相匹配,否则返回nil
dateFormatter.dateFormat = "yyyy-MM-dd"
var dateFromString = dateFormatter.date(from: dateString)

NSDate to String

var formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"

//其中dateFromString为上面例子的变量
let stringDate: String = formatter.string(from: dateFromString)

Swift3和Swift4不同点在于NSDate改为Date,且在字符串转换为日期时,Swift 4返回的是一个可选类型日期。

处理Swift 4 使用#selector警告:Argument of '#selector' refers to instance method that depends on '@objc' attribute inference deprecated in Swift 4

Swift 3升级到Swift 4,在使用#selector可能会报以下警告:

Argument of '#selector' refers to instance method 'doAction()' in 'ViewController' that depends on '@objc' attribute inference deprecated in Swift 4

原因

如果需要把Swift API暴露给Objective-C,我们需要添加@objc注解。Swift 3以及之前的版本,为了达到最大兼容Objective-C,编译器会在很多地方隐式地添加了@objc,例如所有继承于NSObject的类都会被自动加上@objc。

显然我们很多时候并不需要暴露Swift的API给Objective-C,这些隐式转换给app的包添加了额外的代码。

Swift 4对此做出了调整,NSObject的子类不再自动添加@objc。只有以下这几种情况才会隐式推断为@objc:

  • 重写@objc的声明
class Super {
    @objc func foo() { }
}

class Sub : Super {
    /* 推断为 @objc */
    override func foo() { }
}
  • 实现@objc协议要求的声明
@objc protocol MyDelegate {
    func bar()
}

class MyClass : MyDelegate {
    /* 推断为 @objc */
    func bar() { }
}
  • 声明中属性隐含了@objc,例如@IBAction,@IBOutlet和@NSManaged

其他情况Swift 4要求我们显式添加@objc到那些需要暴露给Objective-C的API。

显式暴露Swift API

使用@objc标记单独的方法

@objc func myFunc() {
   // ... 
}

使用@objc标记多个方法

@objc extension ViewController {
    func objcMethod1() {}
    func objcMethod2() {}
    @nonobjc nonObjcMethod() {}
}

如果不需要暴露的方法,使用@nonobjc标记

使用@objcMembers标记整个类或struct

@objcMembers class ViewController: UIViewController {
    // code
}

问题解决

上面的问题就是需要我们显示添加@objc来解决。

func doAction() { /* ... */ }

// 改为
@objc func doAction() { /* ... */ }

参考:SE-0160:Limiting @objc inference

Swift3.1检测网络连接状态(包含WiFi以及4G)

Reachability.swift

import Foundation
import SystemConfiguration

class Reachability {
    var hostname: String?
    var isRunning = false
    var isReachableOnWWAN: Bool
    var reachability: SCNetworkReachability?
    var reachabilityFlags = SCNetworkReachabilityFlags()
    let reachabilitySerialQueue = DispatchQueue(label: "ReachabilityQueue")
    init?(hostname: String) throws {
        guard let reachability = SCNetworkReachabilityCreateWithName(nil, hostname) else {
            throw Network.Error.failedToCreateWith(hostname)
        }
        self.reachability = reachability
        self.hostname = hostname
        isReachableOnWWAN = true
    }
    init?() throws {
        var zeroAddress = sockaddr_in()
        zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
        zeroAddress.sin_family = sa_family_t(AF_INET)
        guard let reachability = withUnsafePointer(to: &zeroAddress, {
            $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
                SCNetworkReachabilityCreateWithAddress(nil, $0)
            }}) else {
                throw Network.Error.failedToInitializeWith(zeroAddress)
        }
        self.reachability = reachability
        isReachableOnWWAN = true
    }
    var status: Network.Status {
        return  !isConnectedToNetwork ? .unreachable :
                isReachableViaWiFi    ? .wifi :
                isRunningOnDevice     ? .wwan : .unreachable
    }
    var isRunningOnDevice: Bool = {
        #if (arch(i386) || arch(x86_64)) && os(iOS)
            return false
        #else
            return true
        #endif
    }()
    deinit { stop() }
}

extension Reachability {
    func start() throws {
        guard let reachability = reachability, !isRunning else { return }
        var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
        context.info = Unmanaged<Reachability>.passUnretained(self).toOpaque()
        guard SCNetworkReachabilitySetCallback(reachability, callout, &context) else { stop()
            throw Network.Error.failedToSetCallout
        }
        guard SCNetworkReachabilitySetDispatchQueue(reachability, reachabilitySerialQueue) else { stop()
            throw Network.Error.failedToSetDispatchQueue
        }
        reachabilitySerialQueue.async { self.flagsChanged() }
        isRunning = true
    }
    func stop() {
        defer { isRunning = false }
        guard let reachability = reachability else { return }
        SCNetworkReachabilitySetCallback(reachability, nil, nil)
        SCNetworkReachabilitySetDispatchQueue(reachability, nil)
        self.reachability = nil
    }
    var isConnectedToNetwork: Bool {
        return isReachable &&
               !isConnectionRequiredAndTransientConnection &&
               !(isRunningOnDevice && isWWAN && !isReachableOnWWAN)
    }
    var isReachableViaWiFi: Bool {
        return isReachable && isRunningOnDevice && !isWWAN
    }

    /// 此flags表示网络节点或地址是否可达,包括是否需要链接以及建立连接时是否需要用户干预
    var flags: SCNetworkReachabilityFlags? {
        guard let reachability = reachability else { return nil }
        var flags = SCNetworkReachabilityFlags()
        return withUnsafeMutablePointer(to: &flags) {
            SCNetworkReachabilityGetFlags(reachability, UnsafeMutablePointer($0))
            } ? flags : nil
    }

    /// 比较当前的flags和之前的flags,如果有改变,发送一个falgsChanged通知
    func flagsChanged() {
        guard let flags = flags, flags != reachabilityFlags else { return }
        reachabilityFlags = flags
        NotificationCenter.default.post(name: .flagsChanged, object: self)
    }

    /// 指定的节点或地址瞬态连接可达,如PPP。
    var transientConnection: Bool { return flags?.contains(.transientConnection) == true }

    /// 指定的节点或地址使用当前的网络配置连接可达
    var isReachable: Bool { return flags?.contains(.reachable) == true }

    /// 指定的节点或地址使用当前的网络配置连接可达,但必须先建立连接。 如果此flag被设置,kSCNetworkReachabilityFlagsConnectionOnTraffic flag, kSCNetworkReachabilityFlagsConnectionOnDemand flag
, 或kSCNetworkReachabilityFlagsIsWWAN flag也需要设置来指示必须连接的类型。如果需要手动连接, kSCNetworkReachabilityFlagsInterventionRequired flag 也需要设置.
    var connectionRequired: Bool { return flags?.contains(.connectionRequired) == true }

    /// 指定的节点或地址使用当前的网络配置连接可达,但必须先建立连接。指向该名称或地址的任何流量将启动连接。
    var connectionOnTraffic: Bool { return flags?.contains(.connectionOnTraffic) == true }

    /// 指定的节点或地址使用当前的网络配置连接可达,但必须先建立连接。
    var interventionRequired: Bool { return flags?.contains(.interventionRequired) == true }

    /// 指定的节点或地址使用当前的网络配置连接可达,但必须先建立连接。使用CFSocketStream接口按需建立连接(有关此信息,请参阅CFStream Socket Additions)。
    var connectionOnDemand: Bool { return flags?.contains(.connectionOnDemand) == true }

    /// 指定节点或地址与当前系统网络接口相关联。
    var isLocalAddress: Bool { return flags?.contains(.isLocalAddress) == true }

    /// 指定节点或地址的网络流量将不会通过网关,而是直接路由到系统中的接口。
    var isDirect: Bool { return flags?.contains(.isDirect) == true }

    /// 指定节点或地址4G可达
    var isWWAN: Bool { return flags?.contains(.isWWAN) == true }

    /// 指定的节点或地址使用当前的网络配置连接可达,但必须先建立连接。如果此标识被设置。
    /// 指定节点或地址瞬态连接可达,例如PPP
    var isConnectionRequiredAndTransientConnection: Bool {
        return (flags?.intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection]) == true
    }
}

func callout(reachability: SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer?) {
    guard let info = info else { return }
    DispatchQueue.main.async {
        Unmanaged<Reachability>.fromOpaque(info).takeUnretainedValue().flagsChanged()
    }
}

extension Notification.Name {
    static let flagsChanged = Notification.Name("FlagsChanged")
}

struct Network {
    static var reachability: Reachability?
    enum Status: String, CustomStringConvertible {
        case unreachable, wifi, wwan
        var description: String { return rawValue }
    }
    enum Error: Swift.Error {
        case failedToSetCallout
        case failedToSetDispatchQueue
        case failedToCreateWith(String)
        case failedToInitializeWith(sockaddr_in)
    }
}

使用

在AppDelegate didFinishLaunchingWithOptions方法添加使用

do {
    Network.reachability = try Reachability(hostname: "www.google.com")
    do {
        try Network.reachability?.start()
    } catch let error as Network.Error {
        print(error)
    } catch {
        print(error)
    }
} catch {
    print(error)
}

参考:https://stackoverflow.com/a/30743763/1304650

Xcode 9缩放iPhone模拟器快捷键

Xcode 9之后新增了拖拽的方式缩放iPhone模拟器。

Xcode 9.1

界面操作
Menubar >> Window >> Physical Size 或 Pixel Accurate

缩放快捷键

  • ⌘ 1:Physical Size,按物理尺寸缩放
  •  ⌘ 2:Pixel Accurate,按像素缩放

Xcode 9

界面操作
菜单栏 >> Window >> Scale >>缩放的百分比

缩放快捷键

  •  ⌘ 1:50%  
  •  ⌘ 2:100%
  •  ⌘ 3: 200%

Xcode 9之前

缩放快捷键

  • ⌘+1 :100%
  • ⌘+2 : 75%
  • ⌘+3 : 50%
  • ⌘+4 :33%
  • ⌘+5  :25%

下载安装iOS 9.1 模拟器失败:The certificate for this server is invalid

问题

下载iOS 9.1模拟器失败,报错信息:

Could not download and install iOS 9.1 Simulator. The certificate for this server is invalid. You might be connecting to a server that is pretending to be “devimages.apple.com.edgekey.net” which could put your confidential information at risk.

解决方法

这个主要是链接的证书问题,需要更新证书信任设置。

在Safari浏览器打开https://devimages.apple.com.edgekey.net/downloads/xcode/simulators链接,浏览器会提示: 此链接非私人链接。如图

点击visit this website,浏览器会提示更新证书信任设置。更新完证书信任设置后就可以正常下载安装iOS9.1模拟器了。