Start Developing iOS Apps (Swift)->实现自定义控件(二)

添加对Interface Builder的支持

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

image: ../Art/ICC_errorsandwarnings_2x.png

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

image: ../Art/ICC_ambiguouslayoutwarning_2x.png
image: ../Art/ICC_ambiguouslayoutwarning_2x.png

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_assetcatalog_addfolder_2x.png
    image: ../Art/ICC_assetcatalog_addfolder_2x.png
  3. 双击文件名称,重命名为Rating Images。
  4. 选中这个文件,在右下角,点击加号按钮并在弹出菜单中选择New Image Set。
    一个图片集合(image set)代表一个图像资源,但是能够包含图像的不同版本,这些版本是用来在不同屏幕分辨率上显示的。
  5. 双击image set的名字,重命名为emptyStar。
  6. 在电脑中,选择你想添加的空心星星图片。
  7. 拖拽这个图片放到image set的2x插槽内。


    image: ../Art/ICC_emptystar_drag_2x.png

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

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


    image: ../Art/ICC_filledstar_drag_2x.png
  12. 选中这个文件,在右下角,点击加号按钮并在弹出菜单中选择New Image Set。
  13. 双击image set的名字,重命名为highlightedStar。
  14. 在电脑上,选择你想要添加的填充星星图片。
  15. 拖拽这个图片放到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_emptystars_2x.png
image: ../Art/ICC_sim_emptystars_2x.png

实现按钮动作

用户需要能够通过点击星星来选择评分,所以你要使用真正的代码来代替ratingButtonTapped(_:) 方法中调试用的代码。

实现评分动作

  1. 在RatingControl.swift中,找到ratingButtonTapped(button:)方法。
func ratingButtonTapped(button: UIButton) {
            print("Button pressed 👍")
        }
  1. 用下面的代码替换print语句:
func ratingButtonTapped(button: UIButton) {
            guard let index = ratingButtons.index(of: button) else {
                fatalError("The button, \(button), is not in the ratingButtons array: \(ratingButtons)")
            }
            
            // Calculate the rating of the selected button
            let selectedRating = index + 1
            
            if selectedRating == rating {
                // If the selected star represents the current rating, reset the rating to 0.
                rating = 0
            } else {
                // Otherwise set the rating to the selected star
                rating = selectedRating
            }
        }

在上面的代码中,indexOf(:)方法尝试在按钮数组中找这个按钮,并在找到后返回它在数组中的索引值。这个方法返回的是可选类型Int,因为你查找的对象在集合中可能不存在。但是,因为触发该动作的唯一按钮集是你创建的,如果indexOf(:)方法不能找到一个匹配的按钮,那么代码就有了严重的错误。抛出错误、终止应用,并在控制台上打印有用的错误信息,帮助你在设计和测试应用时找到并修复错误。
一旦你有按钮的索引的时候(这个值在0-4之间),你给索引加1来计算评分(就是1-5之间的值)。如果用户点击的星星恰好是当前的评分,你就重置控件的rating属性为0。否则,你就设置rating值为选中的评分值。

  1. 一旦评分值被设置,你需要一些方法来更新按钮的显示。在RatingControl.swift中,在结束的花括号前(}),添加如下方法:
private func updateButtonSelectionStates() {
        }

这个辅助方法可以用来更新按钮的选择状态。

  1. 在updateButtonSelectionStates()方法中,添加如下for-in循环:
private func updateButtonSelectionStates() {
            for (index, button) in ratingButtons.enumerated() {
                // If the index of a button is less than the rating, that button should be selected.
                button.isSelected = index < rating
            }
        }

这个代码遍历按钮数组,并基于评分的位置对每个按钮的选中状态进行设置。就像你较早前看到的,选中状态影响按钮的呈现。如果按钮的索引小于评分,则isSelected属性设置为true,并且按钮显示填充的星星图片。否则,isSelected属性设置为false,按钮显示空心星星图片。

  1. 添加一个属性观察器到rating属性,当rating值改变时都会调用updateButtonSelectionStates()方法。
var rating = 0 {
            didSet {
                updateButtonSelectionStates()
            }
        }
  1. 每当按钮添加到控件的时候,也需要更新按钮的选择状态。在setupButtons()方法中,在方法结束的花括号(})之前添加 updateButtonSelectionStates()方法。现在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
                
                // 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()
        }

检查点:运行应用。你应该看到五颗星星,点击一颗就改变评分。例如,点击第三颗星星把评分改为3。第二次点击同一颗星星,控件将评分重置为零颗星。

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_dragoutlet_2x.png
    image: ../Art/ICC_ratingcontrol_dragoutlet_2x.png
  6. 在弹出的对话框中,Name字段键入ratingControl。
    其他选项不变。你的对话框看上去是这样的:


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

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

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

推荐阅读更多精彩内容