美文网首页Hacking with iOS: SwiftUI Edition
Hacking with iOS: SwiftUI Editio

Hacking with iOS: SwiftUI Editio

作者: 韦弦Zhy | 来源:发表于2021-01-06 19:41 被阅读0次

    \color{red}{\Large \mathbf{Hacking \quad with \quad iOS: SwiftUI \quad Edition}}

    \Large \mathbf{里程碑:项目 \ 16 - 18}

    What you learned - 你学到了什么

    最近我们进行了一些非常漫长的项目,但这主要是由于您的SwiftUI技能真正得到了增长——您现在已经超出了基础知识,因此您能够解决更大的项目来解决更大的问题。我意识到在这些较大的项目上工作会感到很累,但我希望您能够回顾自己的成果并感觉良好——您走了很长一段路!

    在完成这些项目时,您还了解了:

    • 使用@EnvironmentObject读取环境值。
    • 使用TabView创建 Tabs。
    • 使用 Swift 的 Result 类型返回成功或失败信息。
    • 使用objectWillChange.send()手动发布ObservableObject的变更。
    • 控制图像插值。
    • 将按钮放在ContextMenu中。
    • 使用UserNotifications框架创建本地通知。
    • 通过Swift package dependencies 使用第三方代码。
    • 使用map()filter()基于现有数组创建新数组。
    • 如何创建动二维码。
    • 将自定义手势附加到SwiftUI视图。
    • 使用UINotificationFeedbackGenerator使iPhone振动。
    • 使用·allowHitTesting()`控制用户交互。
    • 使用计时器重复触发事件,或通过从NotificationCenter接收事件来触发事件。
    • 支持色盲,减少动画等辅助性功能。
    • 横向使用带有StackNavigationViewStyleNavigationView
    • SwiftUI的三步布局系统。
    • Alignment,alignment guides和自定义alignment guides。
    • 使用position()修饰符绝对定位视图。
    • 使用GeometryReaderGeometryProxy实现特殊效果。

    …并且您还构建了一些真正的应用程序来将这些技能付诸实践——真的很忙,希望您为自己的成就感到自豪!

    Key points - 关键点

    在我们继续进行该项目的挑战之前,我想深入探讨两点,以确保您已充分理解它们:map()filter()如何适应更大的函数式编程世界,以及Swift的Result类型。

    Functional programming - 函数式编程

    尽管我在《Pro Swift》一书中对函数式编程进行了很多介绍,但我也想在这里进行介绍,因为我们在项目16中使用了两次,一次是与map()一起使用,一次是与filter()一起使用。这两种方法都是为了让我们指定想要的东西,而不是如何达到目的而设计的,这两种方法都是广泛编程方法的一部分它被称为函数式编程。

    为了演示此方法与称为命令式编程的通用替代方法有何不同,请看以下代码:

    let numbers = [1, 2, 3, 4, 5]
    var evens = [Int]()
    
    for number in numbers {
        if number.isMultiple(of: 2) {
            evens.append(number)
        }
    }
    

    这将创建一个整数数组,一个一个地循环遍历,然后将2的倍数添加到名为偶数的新数组中——我们需要确切说明我们希望过程如何发生。该代码易于阅读,易于编写并且运行良好,但是如果我们要使用filter()重写它,则会得到以下信息:

    let numbers = [1, 2, 3, 4, 5]
    let evens = numbers.filter { $0.isMultiple(of: 2) }
    

    现在,我们不需要弄清楚事情应该如何发生,而只需关注我们想要发生的事情:我们为filter()提供可以执行的测试,其余的都可以自动完成。这意味着我们的代码更短,很棒,但是它还通过其他三种方式得到了改进:

    1. 不再可能在循环内插入意外break —— filter()将始终处理数组中的每个元素,这种额外的简单性意味着我们可以专注于测试本身。
    2. 除了提供闭包之外,我们还可以调用共享函数,这对于代码重用非常有用。
    3. 现在,所得的evens数组是恒定的,因此以后我们不能无意中对其进行修改。

    编写更少的代码总是很不错,但是编写更简单,更可重用且变量更少的代码则更好!

    \color{red}{\mathbf{编写更少的代码总是很不错,但是编写更简单,更可重用且变量更少的代码则更好!}}

    接受函数作为参数或将函数作为返回值的函数称为高阶函数,而map()filter()都是它的示例。 Swift 还有更多类似的东西,但是最有用的之一是compactMap()

    1. 就像map()一样,对数组中的每个项目运行转换函数。
    2. 将该转换函数返回的所有可选参数解包,并将结果放入要返回的新数组中。
    3. 任何为nil的可选项都将被丢弃。

    因此,虽然map()将创建一个新数组,其中包含与其所使用的数组相同数量的项目,但compactMap()可能返回相同数量,更少的项目,甚至根本没有!

    要查看实际的map()compactMap()之间的区别,请尝试以下示例:

    let numbers = ["1", "2", "fish", "3"]
    let evensMap = numbers.map(Int.init)
    let evensCompactMap = numbers.compactMap(Int.init)
    

    这将创建一个字符串数组,然后使用map()compactMap()将其转换为整数数组。运行该代码时,evensMap将包含两个可选整数,然后是nil,然后是另一个可选整数,而evensCompactMap将包含三个实整数——没有可选项,也没有nil。好多了!

    Result

    我们使用Swift的Result类型作为返回成功或失败的单个值的简单方法,但是我认为有一些重要功能对您自己的代码有用。

    首先,如果您考虑一下,结果就像是一个稍微高级的可选形式。可选参数要么包含某种值(整数,字符串等),要么根本不包含任何值,而Result也包含某种值,但是对于替代情况而言,Result现在不包含任何值,它必须包含某种错误。

    在幕后,可选值和Result都实现为带有两种情况的Swift枚举。对于可选值,该枚举称为Optionalnil对应.none,.some对应您的整数/字符串/等关联值;对于Result,它们是.success,具有关联的值,.failure是另一个关联的值。

    两者之间的唯一真正区别是,Swift的可选选项使用了语法糖——特殊语法旨在使我们的生活更轻松,因为可选项非常常见。因此,对于可选对象来说,存在if let和可选链之类的东西,而Result则没有任何特殊代码。

    其次,您已经看到Result包含某种成功值或某种错误值,但是如果您需要它,Result可以抛出异常函数方法。

    如果您有一个Result并想使用do/catch,只需调用Resultget()方法——如果成功 -> 值存在 -> 它将返回成功值,否则将抛出错误。

    例如,如下代码:

    enum NetworkError: Error {
        case badURL
    }
    
    func createResult() -> Result<String, NetworkError> {
        return .failure(.badURL)
    }
    
    let result = createResult()
    

    它定义了某种错误,创建了一个返回字符串或错误的函数(但实际上总是返回一个错误),然后调用该函数并将其返回值放入结果中。如果要使用具有该值的do / catch,可以按如下的方式使用get()

    do {
        let successString = try result.get()
        print(successString)
    } catch {
        print("Oops! There was an error.")
    }
    

    反之——从抛出代码创建Result值——您会发现Result具有一个接受抛出闭包的初始化程序。如果闭包返回一个正常可用的值,则会赋值为成功的case,否则将引发的错误放入失败的case。

    例如:

    let result = Result { try String(contentsOf: someURL) }
    

    在该代码中,结果将为Result<String, Error>——它没有特定类型的Error,因为String(contentsOf :)没有返回。

    关于Result,您应该了解的最后一件事是它具有您已经习惯的功能方法,包括map()mapError()。例如,map()方法在Result内部查找,并使用您指定的闭包将成功值转换为另一种值——例如,它可能会将字符串转换为整数。但是,如果发现失败,它将直接使用它,而忽略您的转换。另外,mapError()可以将错误从一种类型转换为另一种类型,如果您想在一个位置上统一错误类型,这可能会很有用。

    这是关于函数式编程的众多爱好之一:一旦了解了map()的“采用闭包并将其用于转换东西”的性质,您就会发现它存在于数组,Result甚至Optional中!

    Challenge - 挑战

    这次挑战可能很容易,也可能很难,具体取决于您想挑战多远,但该项目的核心很简单:您需要构建一个可帮助用户掷骰子然后存储其结果的应用程序。

    至少应该有一个选项卡视图,其中第一个选项卡允许用户掷骰子,第二个选项卡显示先前掷骰的结果。但是,如果您想进一步提高自己的实力,可以尝试以下一种或多种方法:

    1. 让用户自定义滚动的骰子:骰子的数量和类型:4面,6面,8面,10面,12面,20面,甚至100面。
    2. 显示掷骰子的总数。
    3. 使用 Core Data 存储结果,使结果持久化。
    4. 掷骰子时添加触控反馈。
    5. 对于真正的挑战,请在决定最终数字之前,使骰子滚动的值经过各种可能的值。

    当我说“掷骰子”时,您无需创建精美的3D效果-只需显示“掷骰子”的数字即可。

    唯一可能需要您做些事情的是步骤5:在确定最终数字之前,使结果在各种值之间滑动。解决此问题的最简单方法是通过在一定数量的调用后取消Timer的计时器,但是如果您想使用更高级的解决方案,则可以尝试使用增加的延迟来调用DispatchQueue.main.asyncAfter(),这样它的启动速度快于其减速速度, “骰子滚”放慢。

    在工作时,请花点时间记住代码的可访问性——尝试将其与VoiceOver一起使用,并确保它能正常工作。

    译自 :
    What you learned
    Key points
    Challenge

    相关文章

      网友评论

        本文标题:Hacking with iOS: SwiftUI Editio

        本文链接:https://www.haomeiwen.com/subject/lcyqoktx.html