多年iOS开发经验总结(三)--swift专题

1、swift中使用标记(oc中的#pragma mark)

// MARK: 你的标记
...

// TODO: 你的待办
...

// FIXME: 待修复的内容
...

2、for...in遍历数组同时拿到下标和对应的元素

for (index, element) in arr.enumerated() {
    print("index: \(index), element: \(element)")
}

3、UIButton默认图片在左文字在右,有时候需要交换图片和文字位置(图片在右文字在左)

  • 方法1,semanticContentAttribute属性,要求iOS9+
button.semanticContentAttribute = .forceRightToLeft
  • 方法2,修改titleEdgeInsets和imageEdgeInsets
button.sizeToFit()
button.titleEdgeInsets = UIEdgeInsets(top: 0, left: -button.imageView!.frame.size.width, bottom: 0, right: button.imageView!.frame.size.width)
button.imageEdgeInsets = UIEdgeInsets(top: 0, left: button.titleLabel!.frame.size.width, bottom: 0, right: -button.titleLabel!.frame.size.width)
  • 方法3,直接改transform
button.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
button.titleLabel?.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
button.imageView?.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
  • 方法4,子类化UIButton,重写 func layoutSubviews() 方法
class WZBButton: UIButton {
    override func layoutSubviews() {
        super.layoutSubviews()
        
        /// 文字在左,图片在右
        if let imageView = imageView, let titleLabel = titleLabel, titleLabel.frame.minX > imageView.frame.minX {
            var imageViewFrame = imageView.frame
            var titleLabelFrame = titleLabel.frame
            
            titleLabelFrame.origin.x = imageViewFrame.minX
            imageViewFrame.origin.x = titleLabelFrame.maxX
            
            imageView.frame = imageViewFrame
            titleLabel.frame = titleLabelFrame
        }
    }
}
  • 方法5,子类化UIButton,重写 func titleRect(forContentRect contentRect: CGRect) -> CGRectfunc imageRect(forContentRect contentRect: CGRect) -> CGRect 方法
class WZBButton: UIButton {
    override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
        var rect = super.titleRect(forContentRect: contentRect)
        rect.origin.x = 0
        return rect
    }
    override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
        var rect = super.imageRect(forContentRect: contentRect)
        rect.origin.x = contentRect.maxX - rect.width
        return rect
    }
}

5、用weak修饰protocol

如果这样写,会报错

protocol MyProtocol {
    func test()
}
class MyClass {
    /// 报错信息
    /// 'weak' must not be applied to non-class-bound 'MyProtocol'; consider adding a protocol conformance that has a class bound
    weak var delegate: MyProtocol?
}

正确的写法是让这个协议继承AnyObject(这样写之后,这个协议就只能被类遵守),修改后的写法:

/// 正确写法
protocol MyProtocol: AnyObject {
    func test()
}
class MyClass {
    weak var delegate: MyProtocol?
}

6、像UITableView的tableHeaderVIew一样为UICollectionview添加headerView

let headerHeight = 100
collectionView.contentInset = UIEdgeInsets(top: headerHeight, left: 0, bottom: 0, right: 0)
headerView = YourHeaderView()
headerView?.frame = CGRect(x: 0, y: -headerHeight, width: collectionView.width, height: headerHeight)

7、Int和String互相转换

/// Int to String
let i = 10
let str = String(i)
print("String is : \(str)")

/// String to Int
let num = Int(str)
/// 因为转换过来是可选的,所以需要先尝试解包
if let num = num {
    print("Int is : \(num)")
}

8、系统UIButton设置文字的时候会有动画效果,如何关闭动画效果

  • 方法一、禁用UIView动画
UIView.setAnimationsEnabled(false)
button.setTitle(title, for: .normal)
button.layoutIfNeeded()
UIView.setAnimationsEnabled(true)
  • 方法二、使用performWithoutAnimation方法
UIView.performWithoutAnimation {
    button.setTitle(title, for: .normal)
    button.layoutIfNeeded()
}

9、查看本地swift版本

swift版本是和Xcode版本一一对应的,一般来说打开终端运行,云心命令就能看到:

xcrun swift -version

我的Xcode版本是11.1,运行这个命令行打印的是:

Apple Swift version 5.1 (swiftlang-1100.0.270.13 clang-1100.0.33.7)
Target: x86_64-apple-darwin19.0.0

有种情况是你的电脑上安装了多个Xcode(比如你安装了某个Beta版本),这时候可以使用以下命令修改路径:

sudo xcode-select -s /Applications/Xcode_Beta.app

10、项目中的swift版本

  • 项目中指定使用swfit某个版本
    选择target -> build settings -> 搜索swift_version -> 选择指定版本


    指定使用swfit某个版本
  • 代码中判断swift版本

#if swift(>=5.2)
print("Swift版本 >= 5.2")

#elseif swift(>=5.1)
print("Swift版本 >= 5.1")

#elseif swift(>=5.0)
print("Swift版本 >= 5.0")

#elseif swift(>=4.2)
print("Swift版本 >= 4.2")

#elseif swift(>=4.1)
print("Swift版本 >= 4.1")

#elseif swift(>=4.0)
print("Swift版本 >= 4.0")

#elseif swift(>=3.2)
print("Swift版本 >= 3.2")

#elseif swift(>=3.0)
print("Swift版本 >= 3.0")

#elseif swift(>=2.2)
print("Swift版本 >= 2.2")

#elseif swift(>=2.1)
print("Swift版本 >= 2.1")

#elseif swift(>=2.0)
print("Swift版本 >= 2.0")

#elseif swift(>=1.2)
print("Swift版本 >= 1.2")

#elseif swift(>=1.1)
print("Swift版本 >= 1.1")

#elseif swift(>=1.0)
print("Swift版本 >= 1.0")

#endif

11、数组合并

  • 方法一
let a = [1, 2, 3]
let b = [4, 5, 6]
let c = a + b
print(c)
  • 方法二
var a = [1, 2, 3]
let b = [4, 5, 6]
a.append(contentsOf: b)
print(a)
  • 方法三
var a = [1, 2, 3]
let b = [4, 5, 6]
a += b
print(a)

12、UIViewController点击空白处隐藏键盘

  • 方法一,在ViewController中重写touchBegin方法
override func touchesBegan(touches: Set, withEvent event: UIEvent?) {
    view.endEditing(true)
}
  • 方法二,在UIViewController的分类中为View添加一个单击手势
private var hideKeyboardWhenTappedAroundKey: UInt8 = 0
private var tapGestureKey: UInt8 = 0
extension UIViewController {
    var hideKeyboardWhenTappedAround: Bool {
        get { (objc_getAssociatedObject(self, &hideKeyboardWhenTappedAroundKey) as? Bool) ?? false }
        set {
            objc_setAssociatedObject(self, &hideKeyboardWhenTappedAroundKey, newValue, .OBJC_ASSOCIATION_ASSIGN)
            if newValue {
                if tapGesture == nil {
                    tapGesture = UITapGestureRecognizer(target: self, action: #selector(hiddenKeyBoard))
                    view.addGestureRecognizer(tapGesture!)
                }
            } else {
                if let tapGesture = tapGesture {
                    view.removeGestureRecognizer(tapGesture)
                    self.tapGesture = nil
                }
            }
        }
    }
    private var tapGesture: UITapGestureRecognizer? {
        get { objc_getAssociatedObject(self, &tapGestureKey) as? UITapGestureRecognizer }
        set { objc_setAssociatedObject(self, &tapGestureKey, newValue, .OBJC_ASSOCIATION_RETAIN) }
    }
    @objc private func hiddenKeyBoard() {
        view.endEditing(true)
    }
}

然后在需要的控制器中:

override func viewDidLoad() {
    super.viewDidLoad()
    self.hideKeyboardWhenTappedAround = true
}

13、获取当前设备型号(包括iPhone、iPad、iPod、Apple TV、HomePod和模拟器)

  • 方法一,在ViewController中重写touchBegin方法

public extension UIDevice {
    static let modelName: String = {
        var systemInfo = utsname()
        uname(&systemInfo)
        let machineMirror = Mirror(reflecting: systemInfo.machine)
        let identifier = machineMirror.children.reduce("") { identifier, element in
            guard let value = element.value as? Int8, value != 0 else { return identifier }
            return identifier + String(UnicodeScalar(UInt8(value)))
        }
        func mapToDevice(identifier: String) -> String {
            #if os(iOS)
            switch identifier {
            case "iPod5,1":                                 return "iPod touch (5th generation)"
            case "iPod7,1":                                 return "iPod touch (6th generation)"
            case "iPod9,1":                                 return "iPod touch (7th generation)"
            case "iPhone3,1", "iPhone3,2", "iPhone3,3":     return "iPhone 4"
            case "iPhone4,1":                               return "iPhone 4s"
            case "iPhone5,1", "iPhone5,2":                  return "iPhone 5"
            case "iPhone5,3", "iPhone5,4":                  return "iPhone 5c"
            case "iPhone6,1", "iPhone6,2":                  return "iPhone 5s"
            case "iPhone7,2":                               return "iPhone 6"
            case "iPhone7,1":                               return "iPhone 6 Plus"
            case "iPhone8,1":                               return "iPhone 6s"
            case "iPhone8,2":                               return "iPhone 6s Plus"
            case "iPhone9,1", "iPhone9,3":                  return "iPhone 7"
            case "iPhone9,2", "iPhone9,4":                  return "iPhone 7 Plus"
            case "iPhone8,4":                               return "iPhone SE"
            case "iPhone10,1", "iPhone10,4":                return "iPhone 8"
            case "iPhone10,2", "iPhone10,5":                return "iPhone 8 Plus"
            case "iPhone10,3", "iPhone10,6":                return "iPhone X"
            case "iPhone11,2":                              return "iPhone XS"
            case "iPhone11,4", "iPhone11,6":                return "iPhone XS Max"
            case "iPhone11,8":                              return "iPhone XR"
            case "iPhone12,1":                              return "iPhone 11"
            case "iPhone12,3":                              return "iPhone 11 Pro"
            case "iPhone12,5":                              return "iPhone 11 Pro Max"
            case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4":return "iPad 2"
            case "iPad3,1", "iPad3,2", "iPad3,3":           return "iPad (3rd generation)"
            case "iPad3,4", "iPad3,5", "iPad3,6":           return "iPad (4th generation)"
            case "iPad6,11", "iPad6,12":                    return "iPad (5th generation)"
            case "iPad7,5", "iPad7,6":                      return "iPad (6th generation)"
            case "iPad7,11", "iPad7,12":                    return "iPad (7th generation)"
            case "iPad4,1", "iPad4,2", "iPad4,3":           return "iPad Air"
            case "iPad5,3", "iPad5,4":                      return "iPad Air 2"
            case "iPad11,4", "iPad11,5":                    return "iPad Air (3rd generation)"
            case "iPad2,5", "iPad2,6", "iPad2,7":           return "iPad mini"
            case "iPad4,4", "iPad4,5", "iPad4,6":           return "iPad mini 2"
            case "iPad4,7", "iPad4,8", "iPad4,9":           return "iPad mini 3"
            case "iPad5,1", "iPad5,2":                      return "iPad mini 4"
            case "iPad11,1", "iPad11,2":                    return "iPad mini (5th generation)"
            case "iPad6,3", "iPad6,4":                      return "iPad Pro (9.7-inch)"
            case "iPad6,7", "iPad6,8":                      return "iPad Pro (12.9-inch)"
            case "iPad7,1", "iPad7,2":                      return "iPad Pro (12.9-inch) (2nd generation)"
            case "iPad7,3", "iPad7,4":                      return "iPad Pro (10.5-inch)"
            case "iPad8,1", "iPad8,2", "iPad8,3", "iPad8,4":return "iPad Pro (11-inch)"
            case "iPad8,5", "iPad8,6", "iPad8,7", "iPad8,8":return "iPad Pro (12.9-inch) (3rd generation)"
            case "AppleTV5,3":                              return "Apple TV"
            case "AppleTV6,2":                              return "Apple TV 4K"
            case "AudioAccessory1,1":                       return "HomePod"
            case "i386", "x86_64":                          return "Simulator \(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "iOS"))"
            default:                                        return identifier
            }
            #elseif os(tvOS)
            switch identifier {
            case "AppleTV5,3": return "Apple TV 4"
            case "AppleTV6,2": return "Apple TV 4K"
            case "i386", "x86_64": return "Simulator \(mapToDevice(identifier: ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] ?? "tvOS"))"
            default: return identifier
            }
            #endif
        }
        return mapToDevice(identifier: identifier)
    }()
}

调用的时候:

print("当前设备为:\(UIDevice.modelName)")

来自stackoverflow

14、获取当前App版本号

if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] {
    print("当前App版本号:\(version)")
}

15、protocol中创建可选方法

  • 方法一,在分类中使用默认实现
protocol MyProtocol {
    func test()
}

extension MyProtocol {
    /// 这里有了默认实现之后
    /// 这个方法对于其他遵守MyProtocol的类来说就是可选的了
    /// 可以实现也可以不实现
    func test() { }
}
  • 方法二,使用@objc optional
@objc protocol MyProtocol {
    @objc optional func test()
}

不建议用方法二,它的弊端是整个protocol需要用@objc修饰,这就破坏了swift中protocol的优势,使用@objc之后,这个协议只能被类准守,不能被结构体和枚举准守了。还有个缺点是一旦这样写,又会回到OC时代调用协议的方法时要先判断是否实现了这个方法,这样很麻烦。因此建议用方法一

16、数组转字符串

var a = ["1", "2", "3"]
print(a.joined(separator: "-")) // "1-2-3"

注意:数组的joined方法只当数组中的元素类型为字符串的时候可以调用
如果数组中的元素不是字符串,可以像下边这样先转化一下:

var a = [1, 2, 3]
let b = a.map({ $0.description })
print(b.joined(separator: "-")) // "1-2-3"

17、URL转String

let url = URL(string: "https://baidu.com")
let stringUrl = url?.absoluteString

NSURL同样使用absoluteString属性转换

18、==和===的区别

  • ==是比较双方的值是否相等,强调的是值相等,比如:
let a = 1
let b = 1
print(a == b) // true

如果是基本数据类型是可以用==直接比较的,但如果是自定义的枚举、结构体和类,就需要实现Equatable协议,并且重写==运算符方法,代码如下:

class Person: Equatable {
    var id: Int
    init(id: Int) {
        self.id = id
    }
    static func ==(lhs: Person, rhs: Person) -> Bool { lhs.id == rhs.id }
}
let p1 = Person(id: 100000)
let p2 = Person(id: 100000)
print(p1 == p2) // true
  • ===是比较双方的指针是否指向同一个实例,只能用来比较引用类型,因为引用类型才涉及到指针指向的问题:
class A { }
let a1 = A()
let a2 = A()
let a3 = a1
/// a1和a2的地址指向的是不同的内存
print(a1 === a2) // false
/// a1和a3的地址指向的是相同的内存
print(a1 === a3) // true

19、生成随机的UUID字符串

/// uuid is : 59373CDA-F5FF-4D16-B85A-E3F1A5542F8A
print("uuid is : \(UUID().uuidString)")

20、字符串的截取

  • 直接使用str[startIndex...endIndex]
let str = "1234567"
let index = str.index(str.startIndex, offsetBy: 3)
let newStr = String(str[..
  • 使用的时候最好封装到分类中
extension String {
    func substring(start: Int, end: Int) -> String {
        let startIndex = self.index(self.startIndex, offsetBy: start)
        let endIndex = self.index(self.startIndex, offsetBy: end)
        return String(self[startIndex...endIndex])
    }
}

使用的时候直接调用substring方法:

let str = "1234567"
print("前3个字符:\(str.substring(start: 0, end: 2))") // 前3个字符:123
print("第3到第5个字符:\(str.substring(start: 2, end: 4))") // 第3到第5个字符:345

21、设置UILabel的行间距

  • 方法一,使用xib
20200205143528329.gif
  • 方法二,使用代码
extension UILabel {
    /// 设置行间距和行高
    /// - Parameters:
    ///   - lineSpacing: 行间距
    ///   - lineHeightMultiple: 行高
    func setLineSpacing(lineSpacing: CGFloat = 0.0, lineHeightMultiple: CGFloat = 0.0) {
        guard let labelText = self.text else { return }
        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.lineSpacing = lineSpacing
        paragraphStyle.lineHeightMultiple = lineHeightMultiple
        let attributedString:NSMutableAttributedString
        if let labelattributedText = self.attributedText {
            attributedString = NSMutableAttributedString(attributedString: labelattributedText)
        } else {
            attributedString = NSMutableAttributedString(string: labelText)
        }
        attributedString.addAttribute(.paragraphStyle, value:paragraphStyle, range:NSMakeRange(0, attributedString.length))
        self.attributedText = attributedString
    }
}

使用:

label.text = "我是测试文本我是测试文本我是测试文本我是测试文本我是测试文本我是测试文本我是测试文本我是测试文本我是测试文本我是测试文本我是测试\n文本我是测试文本我是测试文本我是测试文本我是测试文本我是测试文本我是测试文本我是测试文本"
label.setLineSpacing(lineSpacing: 5)

22、UIPanGestureRecognizer只是别垂直方向或者水平方向

  • 实现UIGestureRecognizerDelegate中的func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool方法
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
    let velocity = pan.velocity(in: contentView)
    if 只是别垂直 { return abs(velocity.y) > abs(velocity.x) }
    if 只是别水平 { return abs(velocity.x) > abs(velocity.y) }
    return true
}

23、判断当前设备是模拟器还是真机

#if targetEnvironment(simulator)
    print("当前设备为模拟器")
#else
    print("当前设备为真机")
#endif

24、在初始化的过程中调用某个属性的didSet方法

  • 在初始化方法中使用defer延时赋值
class A {
    var i = 0 {
        didSet {
            print(i)
        }
    }
    init(i: Int) {
        defer { self.i = i }
    }
}

_ = A(i: 10)

25、在开发中经常遇到数组越界导致的崩溃,可以利用swift的可选值特性防止这种问题发生

extension Array {
    subscript(safe index: Index) -> Element? {
        indices.contains(index) ? self[index] : nil
    }
}

使用:

let arr = [1, 2, 3]
print(arr[safe: 5]) /// nil

26、判断当前设备是iPhone还是iPad

func isPad() -> Bool { UIDevice.current.userInterfaceIdiom == .pad }
func isPhone() -> Bool { UIDevice.current.userInterfaceIdiom == .phone }

print(isPhone()) // true
print(isPad()) // false

27、字符串中使用换行

  • 方法一
let str = """
hello world!
hello swift!
"""
  • 方法二
let str = "hello world!\n" + "hello swift!"

28、数组去重

老问题了,方法有很多,我这里列举几个我认为简单并且常用的方法:

  • 方法一,分类中给Array添加方法,手动遍历过滤
extension Array where Element: Hashable {
    func removeDuplicates() -> [Element] {
        var result: [Element] = []
        for element in self {
            if !result.contains(element) {
                result.append(element)
            }
        }
        return result
    }
}

let arr = [1, 3, 4, 4, 4]
print(arr.removeDuplicates()) // [1, 3, 4]
  • 方法二,利用Set元素不能重复的特性,但这种方法不能保证数组顺序
let arr = [1, 3, 4, 4, 4]
print(Array(Set(arr))) // [3, 1, 4]
  • 方法三,根据条件过滤(不推荐使用,时间复杂度较高)
extension Array {
    func removeDuplicates(where predicate: (_ lhs: Element, _: Element) -> Bool) -> [Element] {
        var result: [Element] = []
        forEach { (element) in
            if !result.contains(where: { predicate($0, element) }) {
                result.append(element)
            }
        }
        return result
    }
}

struct P {
    var name: String
    var age: Int
}

var p = P(name: "wzb", age: 18)
var p1 = P(name: "wzb", age: 19)
var p2 = P(name: "wzb", age: 19)
var p3 = P(name: "wzb", age: 15)

// (name: "wzb", age: 18)、(name: "wzb", age: 19)、(name: "wzb", age: 15)
print([p, p1, p2, p3].removeDuplicates(where: { $0.age == $1.age }))

29、使用系统自带下拉刷新控件

很多人可能还不知道iOS其实是有自带的下拉刷新控件的,名字叫UIRefreshControl,首先看一下API:

// 获取是否正在刷新
open var isRefreshing: Bool { get }
// 设置菊花的颜色
open var tintColor: UIColor!
// 设置标题的富文本
open var attributedTitle: NSAttributedString?
// 开始刷新
open func beginRefreshing()
// 结束刷新
open func endRefreshing()

使用方法如下:

  • 方法一,代码方式
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(refreshAction), for: .valueChanged)
// UIScrollView/UITableView/UICollectionView
scrollView.refreshControl = refreshControl

@objc func refreshAction() {
    // 刷新数据
    ...
    // 结束刷新
    refreshControl?.endRefreshing()
}
  • 方法二,xib方式
    • 在xib中拖入UITableViewController,在属性列表中把refreshing设置为Enabled,支持设置一个title,以及title的字体和align


      xib配置

然后需要在代码中添加监听事件:

override func viewDidLoad() {
    super.viewDidLoad()
    refreshControl?.addTarget(self, action: #selector(refreshAction), for: .valueChanged)
}

@objc func refreshAction() {
    // 刷新数据
    ...
    // 结束刷新
    refreshControl?.endRefreshing()
}

有点需要注意,UIScrollView/UITableView/UICollectionView在iOS才增加了refreshControl属性,因此如果你的app兼容iOS 10以下,在添加这个控件的时候需要加入判断:

if #available(iOS 10.0, *) {
  scrollView.refreshControl = refreshControl
} else {
  scrollView.addSubview(refreshControl)
}

30、在oc中用isKindOfClass方法判断一个对象是否为某个类,或者继承某个类,那在swift中如何使用isKindOfClass呢

  • 方法一
// oc
if ([view isKindOfClass: UILabel.class]) {
 // view是UILabel类型
}

// swift
if view.isKind(of: UILabel.self) {
 // view是UILabel类型
}
  • 方法二
// oc
if ([view isKindOfClass: UILabel.class]) {
 // view是UILabel类型
}

// swift
if let _ = view as? UILabel {
   // view是UILabel类型
}
  • 方法三
// oc
if ([view isKindOfClass: UILabel.class]) {
 // view是UILabel类型
}

// swift
if view is UILabel {
   // view是UILabel类型
}

推荐使用方法二和方法三

31、多种方法删除数组中的元素

  • 删除首个元素
var arr = [1, 2, 3]
let firstValue = arr.removeFirst() // 1
print(arr) // [2, 3]
  • 删除末尾元素
var arr = [1, 2, 3]
let lastValue = arr.removeLast() // 3
print(arr) // [1, 2]
  • 删除所有元素
var arr = [1, 2, 3]
arr.removeAll()
print(arr) // []
  • 删除第n个元素
var arr = [1, 2, 3]
let value = arr.remove(at: 1) // 2
print(arr) // [1, 3]
  • 删除某个已知的元素
var arr = [1, 2, 3]
if let index = arr.firstIndex(of: 2) {
   arr.remove(at: index)
}
print(arr) // [1, 3]
  • 通过range批量删除
var arr = [1, 2, 3, 4, 5]
// 删除第2、3、4个元素
arr[1...3] = []
print(arr) // ["c", "e"]
  • 用过滤的方法一次删除多个元素
var arr = [1, 2, 3, 4]
// 只保留大于2的,其他的删除
arr = arr.filter{ $0 > 2 }
print(arr) // [3, 4]
  • 用removeAll的方法一次删除多个元素
var arr = [1, 2, 3, 4]
// 删除元素小于等于2的元素
arr.removeAll{ $0 <= 2 }
print(arr) // [3, 4]
  • 通过下标数组批量删除元素
var arr = [1, 2, 3, 4, 5]
// 需要删除的下标
let removeIndexs = [0, 1, 2]
arr = arr.enumerated().filter({ !removeIndexs.contains($0.offset) }).map({ $0.element })
print(arr) // [4, 5]
  • 删除另一个数组中的元素
var arr = ["a", "b", "c", "d", "e"]
let removeObjects = ["a", "b", "d"]
arr = arr.filter { !removeObjects.contains($0) }
print(arr) // ["c", "e"]

32、修改UITextField的占位文字颜色

  • 方式一,代码方式,利用富文本
textField.attributedPlaceholder = NSAttributedString(string: "请输入用户名", attributes: [NSAttributedString.Key.foregroundColor : UIColor.red])
  • 方式二,xib方式,添加keyPath:placeholderLabel.textColor


    xib方式占位文字颜色
  • 方式三,两者结合,用runtime为UITextField添加placeholderColor的属性

private var placeholderColorKey: UInt8 = 0
extension UITextField {
    @IBInspectable
    var placeholderColor: UIColor? {
        get { objc_getAssociatedObject(self, &placeholderColorKey) as? UIColor }
        set {
            attributedPlaceholder = NSAttributedString(string: "请输入用户名", attributes: [NSAttributedString.Key.foregroundColor : UIColor.red])
            objc_setAssociatedObject(self, &placeholderColorKey, newValue, .OBJC_ASSOCIATION_RETAIN)
        }
    }
}

代码使用:

textField.placeholderColor = .red

xib使用:


xib使用

33、关于不常用的高阶函数Reduce

  • Reduce可以将一组元素按照指定规则组合在一起生成一个新值,比如求一组数组之和:
var arr = [1, 2, 3, 4]
let sum = arr.reduce(0, +) // 10

其中,第一个参数是初始值,第二个参数是操作符,可以简单理解为sum = 0 + 1 + 2 + 3 + 4
如果reduce函数的定义,第二个参数是个block,那上边的这个例子展开后是这样的:

var arr = [1, 2, 3, 4]
let sum = arr.reduce(0) { (result, value) -> Int in
    // reduce会遍历arr,重复调用这个block
    // 将上次block产生的结果(第一次会把默认值传进来)和本次的值传入进来
    // result是上次的结果,value遍历本次数组取得的值
    return result + value
}
  • 按照这个思路,可以自己简单实现一个reduce
extension Array {
    func wzb_reduce(_ defaultValue: Element, _ eachOperation: ((Element, Element) -> Element)) -> Element? {
        var result: Element?
        for item in self {
            result = eachOperation(result ?? defaultValue, item)
        }
        return result
    }
}
let test_arr = ["A", "p", "p", "l", "e"]
let sum = test_arr.wzb_reduce("Hello ") { (result, value) -> String in
    result + value
}
sum // "Hello Apple"

喜欢可以随手点个喜欢或者关注一下哦!
您的支持是我最大的动力!

你可能感兴趣的:(多年iOS开发经验总结(三)--swift专题)