多年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<UITouch>, 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[..<index])
print("前三个字符:\(newStr)") // 前三个字符:123

let index1 = str.index(str.endIndex, offsetBy: -3)
let newStr1 = String(str[index1...])
print("后三个字符:\(newStr1)") // 后三个字符:567

let startIndex = str.index(str.startIndex, offsetBy: 2)
let endIndex = str.index(str.startIndex, offsetBy: 4)
let newStr2 = String(str[startIndex...endIndex])
print("第3到第5个字符:\(newStr2)") // 第3到第5个字符:345
  • 使用的时候最好封装到分类中
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"

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345