27 July 2015

原文:原文10 Practical Tips for iOS Developers Using Storyboards

翻译: 使用Storyboards开发的10个小技巧


在这里我将着重讲述10件事情,而不会去全面讲述如何使用Storyboard去创建一些事物。这些知识点没有特定的顺序,但它们也许可以帮到你在这条道路上前行。

Storyboard是我花时间钻研最多的一个领域。我非常喜欢可视化编程。只需要简单地将项目拖到画布中,更新位置信息,再设置一些描述信息,就已创建了一个用户界面而不用编写任何代码。这非常重要,因为用户界面的代码可以很快让你的代码变得一团糟。

当我参与到一个新的项目时,我首先要做的就是找到其中的Storyboard。这是概览程序整体框架的一处重要之地。

假如没有使用可视化编辑器,你就需要手动化操作才能发现工作的进展。你在代码中来回往复耗费大量时间,也只能大致了解给定的视图的情况。您可以明确参考设计文件或运行应用程序并导航到所需的区域,但我更希望避免这种情况。最后,在某些情况下,调整界面组件成为一个繁琐的过程。你不断编译并运行应用程序去验证它们是否都在正确地位置,而不是通过Storyboard快速调整。

1

看看,只是创建右边这么一个简单的界面就编写了这么多的代码,我甚至还没有编写任何自动布局代码来帮助我们定位。我知道会有顽固的代码狂人。但我还是不想这些让我的代码膨胀,这实在是让人不快。不要误会我。对于初学者来说,这样做的价值是理解如何通过代码创建一个用户界面。通过一个给定的用户界面,你有了一个大概的认识,知道它能做什么。而不是查阅文档。

列表


也许可以归为一件,不过不用在意

你不需要将整个程序搭建在一个故事板中。可以将它们分成好几个故事板。假设有管理面板,设置面板,以及主面板。当你的程序扩大后可以节省你的很多精力。在团队中工作时与故事板交互也会变得轻松,而且查找需要的故事板也会更快捷。

什么是exit segue,如何使用它

首先,我说一下什么是segue。假设你现在的故事板中有两个场景,其中第一个场景中有一个按钮。当你右击场景1的按钮,然后拖动到场景2,你这样就是创建了一个segue。

2

现在假设我们选择present modally。该模态表示这是用户优先关注的场景。这没有简单的方法来回到第一个场景,就像你可能看过的如果你推送一个场景到导航栈上。我们可以创建一个委托来通知第一个控制器我们已经完成。但这样有些乏味,我们也可以发送通知给第一个控制器,但这就是有点大材小用。这是一个机会使用exit segue,exit segue就像segue一样工作,不同的是会返回到执行UIStoryboardSegue动作的地方。这就是关键!

通过你创建的segue,exit segue可以一直后退导航,无论segue是在那创建的,都可以找到exit segue。

查看一下下方的完整地视图结构。

3

如果你只是右击按钮的exit segue指示器,你不会看到任何东西。它需要检测你在目标控制器中创建一个UIStoryboardSegue动作的方法(绿色箭头指向)。例如:

@IBAction func unwindToSceneA(unwindSegue: UIStoryboardSegue) {
    // be sure to give your unwind segue an identifier so you know where we're coming from     
}

Storyboard(故事板)跳转

你无需在故事板中绘制漂亮的segue到你想去的地方。你只需初始化故事板以及获取你想要展示的控制器。一旦你有了控制器,你可以调用必要的显示方法。

var storyboard: UIStoryboard = UIStoryboard(name: "Settings", bundle: nil)
    var modal: UIViewController = storyboard.instantiateViewControllerWithIdentifier("settingsStoryboardId") as UIViewController
self.presentViewController(modal, animated: true, completion: nil)

<span class="o">/*</span> <span class="kt">If</span> <span class="n">you</span><span class="err">'</span><span class="n">re</span> <span class="n">fetching</span> <span class="n">a</span> <span class="n">controller</span> <span class="k">in</span> <span class="n">the</span> <span class="n">same</span> <span class="n">storyboard</span> <span class="n">you</span><span class="err">'</span><span class="n">re</span> <span class="n">already</span> <span class="n">on</span><span class="p">,</span> 
 <span class="o">*</span> <span class="n">then</span> <span class="n">you</span> <span class="n">can</span> <span class="n">skip</span> <span class="n">initializing</span> <span class="n">a</span> <span class="k">new</span> <span class="kt">UIStoryboard</span> <span class="n">object</span><span class="o">.</span>
 <span class="o">*/</span>
<span class="k">var</span> <span class="nv">modal</span><span class="p">:</span> <span class="kt">UIViewController</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">storyboard</span><span class="p">?</span><span class="o">.</span><span class="nf">instantiateViewControllerWithIdentifier</span><span class="p">(</span><span class="s">"customStoryboardId"</span><span class="p">)</span> <span class="k">as</span> <span class="kt">UIViewController</span>

self.presentViewController(modal, animated: true, completion: nil)

预览编辑器

如果花所有时间去构建并运行应用程序以观察用户界面是否调整到你想要的结果,是很乏味的。在处理自动布局时尤其如此。

4

现在打开预览编辑器,你可以修改视图,观察它如何变化。你也可以按下左下角助理编辑面板上的+按钮,以便在多个屏幕尺寸预览界面。

让手指休息一下

如果你有一个按钮需要连接到源代码中,你可以右键单击,然后拖一条线到源文件中为你生成一个outlet.

5

此外,对于一个给定的事件,你可以通过单击再拖到你的源文件来生成一个action。

6

以上操作的最终结果。

7

那,为什么需要做这些操作呢? 这么说吧,最明显的就是action。如果你不创建一个@IBAction函数,那么当你按下按钮的时候不会有任何事情发生。你可以假设需要添加一些代码来改变UIImageView中最初设置的的图片。为了改变这个图片我们就需要一个@IBOutlet以便我们访问它。

避免极其复杂的控制器

尽管你的控制器可以管理大量子视图,但你的风险在于一层一层的添加视图,这会使事情完全被破坏。很快你就会发现,你偏离了使用可视化编辑器的目的--提供清晰的视图层次。如果你有一个复杂的视图结构,那么是时候考虑这些设置。

你可以使用一个xib,或者你可以添加一个容器视图对象到你的场景,并隐藏它直到需要的时候。通常我使用xib,但在某些情况下则使用容器视图对象。

当你添加一个xib的同时还会添加一个源文件,你将使用它来初始化xib.例如,我们创建了ExampleView.xib以及一个漂亮的场景。为了加载这个视图我们需要创建ExampleView.swift以及初始化这个xib。

import UIKit

class ExampleView: UIView {

<span class="c1">// normal initialization</span>
<span class="k">override</span> <span class="nf">init</span><span class="p">(</span><span class="nv">frame</span><span class="p">:</span> <span class="kt">CGRect</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">super</span><span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">frame</span><span class="p">:</span> <span class="n">frame</span><span class="p">)</span>
        <span class="k">self</span><span class="o">.</span><span class="nf">addExampleViewSubview</span><span class="p">()</span>
<span class="p">}</span>

<span class="c1">// will be loaded if we use this class in a storyboard, for example</span>
<span class="kd">required</span> <span class="nf">init</span><span class="p">(</span><span class="n">coder</span> <span class="nv">aDecoder</span><span class="p">:</span> <span class="kt">NSCoder</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">super</span><span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">coder</span><span class="p">:</span> <span class="n">aDecoder</span><span class="p">)</span>
        <span class="k">self</span><span class="o">.</span><span class="nf">addExampleViewSubview</span><span class="p">()</span>
<span class="p">}</span>

<span class="kd">func</span> <span class="nf">addExampleViewSubview</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">xib</span> <span class="o">=</span> <span class="kt">NSBundle</span><span class="o">.</span><span class="nf">mainBundle</span><span class="p">()</span><span class="o">.</span><span class="nf">loadNibNamed</span><span class="p">(</span><span class="s">"ExampleView"</span><span class="p">,</span> <span class="nv">owner</span><span class="p">:</span> <span class="k">self</span><span class="p">,</span> <span class="nv">options</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
        <span class="k">var</span> <span class="nv">view</span><span class="p">:</span> <span class="kt">UIView</span> <span class="o">=</span> <span class="n">xib</span><span class="o">.</span><span class="n">first</span> <span class="k">as</span> <span class="kt">UIView</span>
        <span class="n">view</span><span class="o">.</span><span class="n">frame</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">frame</span>
        <span class="k">self</span><span class="o">.</span><span class="nf">addSubview</span><span class="p">(</span><span class="n">view</span><span class="p">)</span>
<span class="p">}</span>

}

占位符约束

这是给那些喜欢混合使用代码与故事板操纵约束的人的。即使我个人尽可能地避免在代码中编写约束,但这对于那些不属于故事板的视图确实是很有作用的。

假如你试图在代码中创建约束,且需要与故事板中的用户界面交互,这可能会是一个非常可怕的经历。不过不要害怕,你可以很轻松地告诉Xcode这个特定约束是一个占位符。这意味着在构建和运行应用程序时它将被忽略。

8

默认的试图控制器

你可能需要更改哪个场景与故事板一起加载。在早期版本的Xcode中你也许会选择一个场景,然后选择Is Initial View Controller。在Xcode的最新版本变了。现在你需要搜索对象库,找到 Storyboard Entry Point,然后你可以拖拽到想要的场景。这其中一次只能有一个是活跃的,所以你可以可以拖拽它到你想想要的任一控制器中。

为什么你会担心入口点发生改变?就我个人而言,我使用它来测试不同的控制器,我不想创建大量的按钮才能进到控制器里去。如果你只是更新故事板的入口点,它就会立即加载。

自定义Segue转场效果

如果你选中一个segue,你可能已经注意到,它有一些预加载的转场效果,比如垂直覆盖、水平翻转、淡入淡出以及部分卷曲等。如果你想要更多的自定义效果呢?在这种情况下你需要创建一个自定义UIStoryboardSegue。

举个简单的粟子,创建一个新的Swift文件,取名为CustomSegue。 当我们的segue被执行的时候(上面的按钮被点击),下面这段代码也会被执行。

import UIKit

class CustomSegue: UIStoryboardSegue {

<span class="k">var</span> <span class="nv">startingPoint</span><span class="p">:</span> <span class="kt">CGPoint</span> <span class="o">=</span> <span class="kt">CGPoint</span><span class="p">(</span><span class="nv">x</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="mi">0</span><span class="p">)</span>

<span class="k">override</span> <span class="kd">func</span> <span class="nf">perform</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">source</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">sourceViewController</span> <span class="k">as</span> <span class="kt">UIViewController</span>
        <span class="k">var</span> <span class="nv">destination</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">destinationViewController</span> <span class="k">as</span> <span class="kt">UIViewController</span>

        <span class="c1">// Add the destination view as a subview (temporarily)</span>
        <span class="n">source</span><span class="o">.</span><span class="n">view</span><span class="p">?</span><span class="o">.</span><span class="nf">addSubview</span><span class="p">(</span><span class="n">destination</span><span class="o">.</span><span class="n">view</span><span class="p">)</span>

        <span class="c1">// Set the start scale</span>
        <span class="n">destination</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">transform</span> <span class="o">=</span> <span class="kt">CGAffineTransformMakeScale</span><span class="p">(</span><span class="mf">0.05</span><span class="p">,</span> <span class="mf">0.05</span><span class="p">)</span>

        <span class="c1">// Original center point</span>
        <span class="k">var</span> <span class="nv">originalCenter</span> <span class="o">=</span> <span class="n">destination</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">center</span>
        <span class="n">destination</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">center</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">startingPoint</span>

        <span class="kt">UIView</span><span class="o">.</span><span class="nf">animateWithDuration</span><span class="p">(</span><span class="mf">0.225</span><span class="p">,</span> <span class="nv">delay</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span> <span class="nv">options</span><span class="p">:</span> <span class="kt">UIViewAnimationOptions</span><span class="o">.</span><span class="kt">CurveEaseOut</span><span class="p">,</span> <span class="nv">animations</span><span class="p">:</span> <span class="p">{</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">Void</span> <span class="k">in</span>
                <span class="n">destination</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">transform</span> <span class="o">=</span> <span class="kt">CGAffineTransformMakeScale</span><span class="p">(</span><span class="mf">1.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">)</span>
                <span class="n">destination</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">center</span> <span class="o">=</span> <span class="n">originalCenter</span>
                <span class="p">})</span> <span class="p">{</span> <span class="p">(</span><span class="n">finished</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Void</span> <span class="k">in</span>
            <span class="n">destination</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="nf">removeFromSuperview</span><span class="p">()</span>
                <span class="n">source</span><span class="o">.</span><span class="nf">presentViewController</span><span class="p">(</span><span class="n">destination</span><span class="p">,</span> <span class="nv">animated</span><span class="p">:</span><span class="kc">false</span><span class="p">,</span> <span class="nv">completion</span><span class="p">:</span><span class="kc">nil</span><span class="p">)</span>
        <span class="p">}</span>
<span class="p">}</span>

}

按下钮按时,这将从我们设置的一个起始位置展开目标视图。在这种情况下,我设置起始位置为场景1中按钮的中心位置(源场景)。

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    let custom = segue as CustomSegue
        custom.startingPoint = self.nextImage.center
}

在故事板文件中我们选择从场景1到场景2的segue。更改Segue为Custom,然后输入Segue类来匹配我们刚刚创建的一个CustomSegue。

避免源代码管理的噩梦

对于那些在团队中的人来说,虽然他们也在逐渐变得更好,但当涉及到源代码管理时,故事板仍是一个大痛处。这也是为什么你应该将故事板拆分成若干个。如果你可以避免这个,请确保同一时刻只有一个人操作故事板。这会避免其他人提交故事板的更改到项目中产生的冲突。即使这并不那么容易去避免,但请做好准备防止这种情况出现。这也许是团队与小公司不想使用故事板最大的原因。

尽管当我与的团队共享故事板时很有可能会导致源代码控制的冲突,我仍然觉得,速度和效益的重要性大于修补因为冲突导致的恼人补丁。在大多数情况下我确保其他人避免与我同时处理。但是,它仍然一次又一次发生。

这里是我如何处理冲突的方法:

(第一道防线)首先主动避免它。在与故事板工作时我经常尽早的提交,并且对其他也在与这个故事板工作的人打个招呼。当与其他人一起工作时,尽量保持小任务量的工作,以及互相帮助。不要推送整体的大更新。 如果我遇到了冲突,我会通过差异工具Kaleidoscope来浏览。这确实需要一些经验来理解故事板的原理。如果你还没看过,现在就是时候。右键点击 storyboard > open as > source code,观察控制器区域(包括连接信息)是如何陈列布局的。 (糟糕的情况)我找出了谁做的最大的更改,谁接收的这些变化信息,覆盖其了其他什么,然后我们会恢复这些改变。 我做过一些大型的项目。虽然这些合并的issues不是经常严重到diff工具不能恢复。但你可以获得更多的经验。

我不能强调第一点是足够的。积极主动很重要。知道你的团队是什么样地情况。如果你有一个令人难以置信的复杂的故事板,就将它板划分成不同的区域。

拿走不谢

故事板是非常有用的,尤其是对于那些非常直观的东西。当做原型时,也可以给你一个不错的速度优势。无需为代码烦忧,你可以快速将各种元素放在适当的地方,连接它们,然后从驱动界面的逻辑开始做起。



blog comments powered by Disqus