iOS 技术文档收录GitHub 中文社区iOS文档翻译

Start Developing iOS Apps (Swift

2017-06-06  本文已影响115人  raingu24

添加对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把用户界面读给用户听。尽管内置控件的默认描述提供了一个很好的开端,但是你可能需要优化用户界面的显示;特别是自定义视图和控件。

在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中打开。
下载文件

上一篇下一篇

猜你喜欢

热点阅读