Start Developing iOS Apps (Swift

作者: raingu24 | 来源:发表于2017-06-06 22:25 被阅读115次

    添加对Interface Builder的支持

    如果你在Interface Builder中看rating控件,你会发现它就是个大的、空的矩形。更糟糕的是,如果你选择rating控件,它的边框将变成红色,这表明rating 控件的布局有问题。事实上,还有另外两个表明可能有问题的迹象。在右侧的Activity viewer(活动查看器)有一个黄色警告三角。在View Controller场景旁边的大纲视图还有一个红色的错误图标。

    image: ../Art/ICC_errorsandwarnings_2x.png

    如果你点击这些图标,Xcode会显示关于这两个错误和警告的更多信息。

    image: ../Art/ICC_missingconstrainterror_2x.png

    这两种情况,根本的原因是一样的。Interface Builder 不知道任何关于rating控件的内容。为了修复它,你需要使用@IBDesignabel来定义控件。它让Interface Builder实例化你的控件的一个副本,并直接将其绘制到画布中。另外,现在Interface Builder具有一个活动的控件副本,它的布局引擎能够正确的定位和设置控件的大小。

    把控件声明为@IBDesignable

    1. 在RatingControl.swift,找到类声明:
        class RatingControl: UIStackView {
    
    1. 在它前面加上 @IBDesignable。
    @IBDesignable class RatingControl: UIStackView {
    
    1. 按下Command-B来构建项目(或者选择 Product > Build)。
    2. 打开Main.storyboard。当构建完成,storyboard将显示rating控件的实时视图。


      image: ../Art/ICC_designableliveview_2x.png

      注意,现在画布正确的设置了RatingControl视图的尺寸和位置。而警告和错误也已经消失。

    Interface Builder能够做很多事,不仅仅是显示你的自定义视图。你能够指定一些属性可以在Attributes Inspector中被设置。添加@IBInspectable属性到所需的属性。Interface Builder支持基本类型(以及相应的可选项)的检查,包括:布尔值、数字、字符串,以及CGRect、CGSize、CDPoint和UIColor。

    添加可检查属性

    1. 在RatingControl.swift中,在//MARK: Properties 部分的下面添加如下属性:
    @IBInspectable var starSize: CGSize = CGSize(width: 44.0, height: 44.0)
            @IBInspectable var starCount: Int = 5
    

    这几行代码定义了按钮的尺寸,并定义了你的控件有多少个按钮。

    1. 现在你需要使用这些值。定位到setupButtons()方法,做如下改变:
    2. 在for-in声明,把数字5改为startCount。
    3. 在 button.heightAnchor.constraint()方法调用,把数字44.0改为starSize.height。
    4. 在 button.widthAnchor.constraint()方法调用,把数字44.0改为starSize.width。现在方法应该如下所示:
    private func setupButtons() {
                
                for _ in 0..<starCount {
                    // Create the button
                    let button = UIButton()
                    button.backgroundColor = UIColor.red
                    
                    // Add constraints
                    button.translatesAutoresizingMaskIntoConstraints = false
                    button.heightAnchor.constraint(equalToConstant: starSize.height).isActive = true
                    button.widthAnchor.constraint(equalToConstant: starSize.width).isActive = true
                    
                    // Setup the button action
                    button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(button:)), for: .touchUpInside)
                    
                    // Add the button to the stack
                    addArrangedSubview(button)
                    
                    // Add the new button to the rating button array
                    ratingButtons.append(button)
                }
            }
    

    如果你切换到 Main.storyboard并选择RatingControl,你将看到Star Size 和Star Count 已设置到了Attributes inspector中。虚线表示控件当前正在使用默认的值(44.0点和5星)。但是现在改变这些值还不会改变控件。


    image: ../Art/ICC_inspectableattributes_2x.png
    1. 要更新控件,你需要在每次这些属性改变的时候重新设置控件的按钮。为了实现它,给每个属性添加一个属性观察器(property observer)。属性观察器在属性值每次被设置时调用,并且可以在值改变之前或之后立刻执行。
    @IBInspectable var starSize: CGSize = CGSize(width: 44.0, height: 44.0) {
                didSet {
                    setupButtons()
                }
            }
             
            @IBInspectable var starCount: Int = 5 {
                didSet {
                    setupButtons()
                }
            }
    

    这里,你为starSize和starCount属性定义了属性观察器。具体来说,didSet属性观察器会在属性值被设置之后立刻被调用。你的实现是调用 setupButtons()方法。这个方法使用更新的尺寸和数量添加新的按钮;但是,这个实现没有摆脱旧的按钮。

    1. 为了清除旧的按钮,在setupButtons() 方法的开始位置添加如下代码:
    // clear any existing buttons
            for button in ratingButtons {
                removeArrangedSubview(button)
                button.removeFromSuperview()
            }
            ratingButtons.removeAll()
    

    这段代码遍历所有的rating控件的按钮。首先,它从stack view管理的视图列表中删除按钮。这告诉stack view它不用再计算这个按钮的尺寸和位置——但按钮仍然是stack view的子视图。接下来,代码把按钮从stack view中完全删除。最后,当所有的按钮都被删除后,代码清空ratingButtons数组。
    现在setupButtons()方法看上去是这样的。

    private func setupButtons() {
                
                // clear any existing buttons
                for button in ratingButtons {
                    removeArrangedSubview(button)
                    button.removeFromSuperview()
                }
                ratingButtons.removeAll()
                
                for _ in 0..<starCount {
                    // Create the button
                    let button = UIButton()
                    button.backgroundColor = UIColor.red
                    
                    // Add constraints
                    button.translatesAutoresizingMaskIntoConstraints = false
                    button.heightAnchor.constraint(equalToConstant: starSize.height).isActive = true
                    button.widthAnchor.constraint(equalToConstant: starSize.width).isActive = true
                    
                    // Setup the button action
                    button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(button:)), for: .touchUpInside)
                    
                    // Add the button to the stack
                    addArrangedSubview(button)
                    
                    // Add the new button to the rating button array
                    ratingButtons.append(button)
                }
            }
    

    注意
    从性能角度上看,删除并替换所有按钮并不是一个好主意。但是,didSet观察器只能在设计的时候被Interface Builder调用。当应用运行时,setupButtons()只被调用一次,在控件第一次从storyboard被加载的时候。因此,没有必要创建更复杂的解决方案来更新现有的按钮。

    检查点:打开Main.storyboard并选择RatingControl对象。尝试改变Start Size和StarCount属性。画布中的控件会发生改变以匹配新的设置。运行应用,你将在模拟器中看到这些改变。

    image: ../Art/ICC_modifyinginspectableproperties_2x.png

    记住,当你测试完了之后,把值改回默认的。

    进一步探索
    更多关于使用自定义视图的信息,见Xcode help中的Lay out user interfaces > Add objects and media > Render custom views。

    添加星星图片到按钮

    接下来,你将添加空的、填充的、以及高亮的星星图片到按钮。


    image: ../Art/ICC_emptyStar_2x.png
    image: ../Art/ICC_filledStar_2x.png
    image: ../Art/ICC_highlightedStar_2x.png

    你可以在课后的下载文件中找到Images文件,从里面找到这些图片,或者使用你自己的图片。(确保图片的名字和你在稍后代码中图片的名字保持一致。)

    添加图片到你的项目

    1. 在project navigator中,选择Assets.xcassets来查看资源目录(asset catalog)。
      回想一下,资源目录是为应用存储和组织图片资源的地方。
    2. 在左下角,点击加号(+)并从弹出菜单选择New Folder。


      image: ../Art/ICC_emptystar_drag_2x.png

      2x是本课你选中的iPhone 7模拟器的显示分辨率。

    3. 选中这个文件,在右下角,点击加号按钮并在弹出菜单中选择New Image Set。
    4. 双击image set的名字,重命名为filledStar。
    5. 在电脑上,选择你想要添加的填充星星图片。
    6. 拖拽这个图片放到image set的2x插槽内。


      image: ../Art/ICC_filledstar_drag_2x.png
    7. 选中这个文件,在右下角,点击加号按钮并在弹出菜单中选择New Image Set。
    8. 双击image set的名字,重命名为highlightedStar。
    9. 在电脑上,选择你想要添加的填充星星图片。
    10. 拖拽这个图片放到image set的2x插槽内。


      image: ../Art/ICC_highlightedstar_drag_2x.png

    你的资源目录看上去是这样的。


    image: ../Art/ICC_assetcatalog_final_2x.png

    接下来,编写代码来在相应的时候为按钮设置合适的图片。

    为按钮设置星星图片

    1. 在RatingControl.swift导航到setupButtons()方法,并且在创建按钮的for-in循环的上面添加如下代码:
    // Load Button Images
            let bundle = Bundle(for: type(of: self))
            let filledStar = UIImage(named: "filledStar", in: bundle, compatibleWith: self.traitCollection)
            let emptyStar = UIImage(named:"emptyStar", in: bundle, compatibleWith: self.traitCollection)
            let highlightedStar = UIImage(named:"highlightedStar", in: bundle, compatibleWith: self.traitCollection)
    

    这些行从资源目录加载星星图片。注意资源目录是在应用的主束(bundle)里。这意味着应用可以使用 UIImage(named:)方法加载图片。但是,因为控件是@IBDesignable,所以代码也需要运行在Interface Builder中。要让图片在Interface Builder中正确的加载,你必须明确指定目录的束。这样就确保系统能找到并加载图片。

    1. 找到设置背景颜色的代码行,并用下面的代码进行替换。
    // Set the button images
            button.setImage(emptyStar, for: .normal)
            button.setImage(filledStar, for: .selected)
            button.setImage(highlightedStar, for: .highlighted)
            button.setImage(highlightedStar, for: [.highlighted, .selected])
    

    按钮有五种不同状态:normal(一般)、高亮(highlighted)、聚焦(focused)、选中(selected)、和禁用(disabled)。默认时,按钮根据它的状态来修改自身的显示,例如,一个禁用的按钮呈现灰色。一个按钮可以在同时呈现多种状态,例如按钮即是禁用又是高亮。
    按钮总是从normal状态开始(不是高亮、选中、聚焦、或者禁用)。无论何时用户点击时,按钮是高亮。你也能用代码设置按钮是选中还是禁用。聚焦状态使用在基于焦点的界面,例如Apple TV。在上面的代码中,你告诉按钮normal状态下,使用空心星星图片。这时按钮默认的图片。每当一个状态或混合状态没有它们自己的图片时,系统就会使用这个图片(可能具有附加效果)。
    接下来,上面的代码为选中状态设置了填充图片。如果你用编码的方式将按钮设置为选中,它将从空心星星变为已填充星星。最后,为高亮状态以及高亮和选中混合状态都设置高亮图片。当用户点击按钮的时候,无论是否选中,系统都会显示高亮按钮图片。

    你的setupButtons()方法看上去是这样了:

            private func setupButtons() {
                
                // Clear any existing buttons
                for button in ratingButtons {
                    removeArrangedSubview(button)
                    button.removeFromSuperview()
                }
                ratingButtons.removeAll()
                
                // Load Button Images
                let bundle = Bundle(for: type(of: self))
                let filledStar = UIImage(named: "filledStar", in: bundle, compatibleWith: self.traitCollection)
                let emptyStar = UIImage(named:"emptyStar", in: bundle, compatibleWith: self.traitCollection)
                let highlightedStar = UIImage(named:"highlightedStar", in: bundle, compatibleWith: self.traitCollection)
                
                for _ in 0..<starCount {
                    // Create the button
                    let button = UIButton()
                    
                    // Set the button images
                    button.setImage(emptyStar, for: .normal)
                    button.setImage(filledStar, for: .selected)
                    button.setImage(highlightedStar, for: .highlighted)
                    button.setImage(highlightedStar, for: [.highlighted, .selected])
                    
                    // Add constraints
                    button.translatesAutoresizingMaskIntoConstraints = false
                    button.heightAnchor.constraint(equalToConstant: starSize.height).isActive = true
                    button.widthAnchor.constraint(equalToConstant: starSize.width).isActive = true
                    
                    // Setup the button action
                    button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(button:)), for: .touchUpInside)
                    
                    // Add the button to the stack
                    addArrangedSubview(button)
                    
                    // Add the new button to the rating button array
                    ratingButtons.append(button)
                }
            }
    

    检查点:运行应用。你将看到星星替代了红色按钮。点击这里的任何按钮让然会调用ratingButtonTapped(_:)并且在控制台上打印消息。当你点击按钮的时候甚至能看到高亮星星,但是你的按钮还没有变为填充图片。你要接着修改。

    image: ../Art/ICC_sim_filledstars_2x.png

    添加辅助信息

    借助iOS内置辅助功能,您可以为每个客户(包括有特殊需求的客户)提供出色的移动体验。 这些功能包括VoiceOver,开关控制,隐藏式字幕或音频描述视频的回放,指导访问,文本到语音等。

    在大多数情况下,用户从这些功能中获得好处而无需任何额外的工作。然而,VoiceOver,通常需要一些额外的工作。VoiceOver是为盲人和低视力用户提供的革命性屏幕阅读功能。VoiceOver把用户界面读给用户听。尽管内置控件的默认描述提供了一个很好的开端,但是你可能需要优化用户界面的显示;特别是自定义视图和控件。

    • 附加功能标签(Accessibility label)。一个简短的本地化单词或短语,简洁的描述这个控件或视图,但是不能辨认元素的类型。例如“添加”或“播放”。
    • 附加功能值(Accessibility value)。一个元素的当前值,当该值不由标签表示时。例如一个滑块(slider)的标签可能是“速度”,但它的当前值可能是“50%”。
    • 附加功能提示(Accessibility hint)。一个简短的本地化短语,用来描述一个元素的动作的结果。例如“添加一个标题”或者“打开购物单”。

    在rating控件中,每个按钮的附加功能标签描述了每个按钮设置的值。例如,第一个按钮标签是“设置一个评分。”附加功能值包含了控件当前的评分。例如,如果你有一个4星的评分,这个值是“4星设置”。最后,你分配一个提示给当前选中的星星,“点击重置评分为零。”所有其他星星的提示值为nil,因为它们的效果是已经被它们的标签描述了。

    添加附加功能标签、值、和提示

    1. 在 RatingControl.swift中,导航到setupButtons()方法,找到for-in声明。
    for index in 0..<starCount {
    
    1. 在for-in循环内部,紧接着约束,添加如下代码:
    // Set the accessibility label
            button.accessibilityLabel = "Set \(index + 1) star rating"
    

    这段代码使用按钮的所以计算标签字符串,然后把它分配到按钮的accessibilityLabel属性。setupButtons()方法看上去是这样的:

    private func setupButtons() {
                
                // Clear any existing buttons
                for button in ratingButtons {
                    removeArrangedSubview(button)
                    button.removeFromSuperview()
                }
                ratingButtons.removeAll()
                
                // Load Button Images
                let bundle = Bundle(for: type(of: self))
                let filledStar = UIImage(named: "filledStar", in: bundle, compatibleWith: self.traitCollection)
                let emptyStar = UIImage(named:"emptyStar", in: bundle, compatibleWith: self.traitCollection)
                let highlightedStar = UIImage(named:"highlightedStar", in: bundle, compatibleWith: self.traitCollection)
                
                for index in 0..<starCount {
                    // Create the button
                    let button = UIButton()
                    
                    // Set the button images
                    button.setImage(emptyStar, for: .normal)
                    button.setImage(filledStar, for: .selected)
                    button.setImage(highlightedStar, for: .highlighted)
                    button.setImage(highlightedStar, for: [.highlighted, .selected])
                    
                    // Add constraints
                    button.translatesAutoresizingMaskIntoConstraints = false
                    button.heightAnchor.constraint(equalToConstant: starSize.height).isActive = true
                    button.widthAnchor.constraint(equalToConstant: starSize.width).isActive = true
                    
                    // Set the accessibility label
                    button.accessibilityLabel = "Set \(index + 1) star rating"
                    
                    // Setup the button action
                    button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(button:)), for: .touchUpInside)
                    
                    // Add the button to the stack
                    addArrangedSubview(button)
                    
                    // Add the new button to the rating button array
                    ratingButtons.append(button)
                }
                
                updateButtonSelectionStates()
            }
    
    1. 导航到updateButtonSelectionStates()方法。在for-in循环内部,紧挨着设置按钮的isSelected属性下面,添加如下代码:
    // Set the hint string for the currently selected star
            let hintString: String?
            if rating == index + 1 {
                hintString = "Tap to reset the rating to zero."
            } else {
                hintString = nil
            }
             
            // Calculate the value string
            let valueString: String
            switch (rating) {
            case 0:
                valueString = "No rating set."
            case 1:
                valueString = "1 star set."
            default:
                valueString = "\(rating) stars set."
            }
             
            // Assign the hint string and value string
            button.accessibilityHint = hintString
            button.accessibilityValue = valueString
    

    这里,你通过检查按钮是否是当前选中的按钮开始。如果它是,你就分配一个提示。如果不是,你就设置按钮的hintString属性为nil。
    接下来,你基于控件的评分计算值。使用switch语句,如果评分是0或1,则分配一个自定义字符串。如果评分大于1,你就使用字符串插值来计算提示内容。最后,分配这些值给accessibilityHint和accessibilityValue属性。

    当用户在VoiceOver可用的环境里运行应用,当用户点击其中一个按钮的时候,VoicePver就会阅读这个按钮的标签,跟在单词按钮后面。然后读附加功能值。最后它读附加功能提示(如果有)。这让用户知道控件当前的值,以及按下当前的按钮会有什么结果。

    进一步探索
    更多关于附加功能的信息,参见Accessibility on iOS.
    还有,因为本课的目的,你只是分配了简单的字符串给附加功能属性;但是,一个产品级的应用应该使用本地化字符串。更多关于国际化和本地化的信息,参见Build Apps for the World。

    连接Rating控件到View Controller

    作为设置rating控件的最后一步,你需要把它的一个引用给ViewController。

    连接rating控件到ViewController.swift

    1. 打开storyboard。
    2. 点击Xcode工具条上的Assistant 按钮来打开助理编辑器。


      image: ../Art/assistant_editor_toggle_2x.png
    3. 想要更大空间,就把project navigator和utility area折叠起来。


      image: ../Art/navigator_utilities_toggle_on_2x.png

      也可以把大纲视图折叠起来。

    4. 选择rating 控件。
      ViewController.swift显示在右侧的编辑器。(如果不是这样,在编辑器选择器栏里选择 Automatic > ViewController.swift)。
    5. 把rating控件拖拽到photoImageView属性的下面。


      image: ../Art/ICC_ratingcontrol_addoutlet_2x.png
    6. 点击连接。

    ViewController类现在有一个引用指向storyboard中的rating控件。

    清理项目

    你已接近完成菜品场景的用户界面了,但在之前你需要做一些清理工作。现在这个FoodTracker应用实现了很多比之前课程更高级的行为和不同的用户界面,你应该移除一些不再需要的部分。你还需要把元素放到栈视图的中心,以平衡界面。

    清理UI

    1. 返回到标准编辑器。


      image: ../Art/standard_toggle_2x.png
    2. 打开storyboard。
    3. 选择Set Default Label Text按钮,然后按下删除键删除它。
      栈视图布置你的界面元素填充按钮留下来的控件。


      image: ../Art/ICC_deletebutton_2x.png
    4. 如果必要,打开大纲视图,选择Stack View对象。


      image: ../Art/ICC_outlineview_2x.png
    5. 打开Attributes inspector
    6. 在Attributes inspector中,找到Alignment(对齐)字段并选择Center。
      在栈视图中的元素都居中对齐:


      image: ../Art/ICC_centerstack_2x.png

    现在,移除和你删掉的按钮对应的action方法。

    清理代码

    1. 打开 ViewController.swift.
    2. 在ViewController.swift中,删除setDefaultLabelText(_:) action方法。
    @IBAction func setDefaultLabelText(sender: UIButton) {
                mealNameLabel.text = "Default Text"
            }
    

    这就是现在你需要删除的全部了。你将在下一课对label outlet(mealNameLabel)作出一些改变。

    检查点:运行应用。所有事都应该和之前一样,只是没有那个删掉的按钮了,并且元素都水平居中了。按钮应该是并排的。点击任何一个按钮仍然调用ratingButtonTapped(_:),并且会恰当的改变按钮的图片。

    重要
    如果你运行出现构建问题,尝试按下Command-Shift-K组合键来清理你的项目。

    image: ../Art/ICC_sim_finalUI_2x.png

    小结

    在本课中,你学习了如何构建一个自定义控件,它能显示在Interface Builder中。这个控件还会在Attributes inspector中显示可修改的属性。最后,你添加了附加功能信息,确保控件能很好的使用Voice Over。

    下一课,你将设计和连接应用的数据模型。

    注意
    想看本课的完整代码,下载这个文件并在Xcode中打开。
    下载文件

    相关文章

      网友评论

        本文标题:Start Developing iOS Apps (Swift

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