<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>流月</title>
  
  
  <link href="/atom.xml" rel="self"/>
  
  <link href="https://flowyears.github.io/"/>
  <updated>2018-08-18T04:03:49.734Z</updated>
  <id>https://flowyears.github.io/</id>
  
  <author>
    <name>flowyears</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>iOS应用加固</title>
    <link href="https://flowyears.github.io/2018/08/17/iOS%E5%BA%94%E7%94%A8%E5%8A%A0%E5%9B%BA/"/>
    <id>https://flowyears.github.io/2018/08/17/iOS应用加固/</id>
    <published>2018-08-17T03:55:29.000Z</published>
    <updated>2018-08-18T04:03:49.734Z</updated>
    
    <content type="html"><![CDATA[<h2 id="完整性校验"><a href="#完整性校验" class="headerlink" title="完整性校验"></a>完整性校验</h2><p>通过检测SignerIdentity判断是Mach-O文件否被篡改</p><p>原理是：SignerIdentity的值在info.plist中是不存在的，开发者不会加上去，苹果也不会，只是当ipa包被反编译后篡改文件再次打包，需要伪造SignerIdentity。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">NSBundle *bundle = [NSBundle mainBundle];</span><br><span class="line">NSDictionary *info = [bundle infoDictionary];</span><br><span class="line">if ([info objectForKey:@&quot;SignerIdentity&quot;] != nil)</span><br><span class="line">&#123;</span><br><span class="line">    return YES;</span><br><span class="line">&#125;</span><br><span class="line">return NO;</span><br></pre></td></tr></table></figure></p><p>参考：<a href="https://www.jianshu.com/p/91aa49c45677" target="_blank" rel="noopener">https://www.jianshu.com/p/91aa49c45677</a></p><h2 id="越狱检测"><a href="#越狱检测" class="headerlink" title="越狱检测"></a>越狱检测</h2><p>参照念茜大神的方法：<a href="https://blog.csdn.net/yiyaaixuexi/article/details/20286929" target="_blank" rel="noopener">iOS安全攻防（二十）：越狱检测的攻与防</a>,很详细的讲述了检测的办法，不过经过测试，发现有的方法在未越狱的设备上也会检测成越狱，可以在使用的时候过滤掉这些方法。</p><h2 id="双击home键后app缩略视图模糊处理"><a href="#双击home键后app缩略视图模糊处理" class="headerlink" title="双击home键后app缩略视图模糊处理"></a>双击<strong>home</strong>键后<strong>app</strong>缩略视图模糊处理</h2><p>有时候双击home键，app展示启动app记录，会有可能暴露敏感信息，可以在app退至后台时做一个模糊处理，然后进入前台后移除模糊效果。<br>实现如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">@interface AppDelegate ()</span><br><span class="line">@property(nonatomic,strong)UIVisualEffectView *effectView;</span><br><span class="line">@end</span><br><span class="line"></span><br><span class="line">@implementation AppDelegate</span><br><span class="line"></span><br><span class="line">- (void)applicationDidEnterBackground:(UIApplication *)application</span><br><span class="line">&#123;</span><br><span class="line">    [self.window addSubview:self.effectView];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (void)applicationWillEnterForeground:(UIApplication *)application</span><br><span class="line">&#123;</span><br><span class="line">    [self.effectView removeFromSuperview];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">-(UIVisualEffectView *)effectView</span><br><span class="line">&#123;</span><br><span class="line">    if (!_effectView)</span><br><span class="line">    &#123;</span><br><span class="line">        UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];</span><br><span class="line">        _effectView = [[UIVisualEffectView alloc] initWithEffect:effect];</span><br><span class="line">        _effectView.frame = self.window.bounds;</span><br><span class="line">    &#125;</span><br><span class="line">    return _effectView;</span><br><span class="line">&#125;</span><br><span class="line">...</span><br><span class="line">@end</span><br></pre></td></tr></table></figure><h2 id="方法名混淆"><a href="#方法名混淆" class="headerlink" title="方法名混淆"></a>方法名混淆</h2><p>也是参照念茜大神的方法：<a href="https://blog.csdn.net/yiyaaixuexi/article/details/29201699" target="_blank" rel="noopener">iOS安全攻防（二十三）：Objective-C代码混淆</a>.</p><h2 id="明文字符串混淆"><a href="#明文字符串混淆" class="headerlink" title="明文字符串混淆"></a>明文字符串混淆</h2><p>参考这篇文章：<a href="https://www.jianshu.com/p/49e98b8a05fd" target="_blank" rel="noopener">iOS字符串硬编码混淆</a>.</p><p>可能作者的步骤描述的不是很清楚，我在这里简单描述一下：<br>1.在项目的<code>.pch</code>文件中，添加如下代码</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">#include &quot;GolobalCFile.h&quot;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">//字符串混淆加密 和 解密的宏开关</span><br><span class="line">//#define ggh_confusion</span><br><span class="line">#ifdef ggh_confusion</span><br><span class="line">#define confusion_NSSTRING(string) [NSString stringWithUTF8String:decryptConstString(string)]</span><br><span class="line">#define confusion_CSTRING(string) decryptConstString(string)</span><br><span class="line">#else</span><br><span class="line">#define confusion_NSSTRING(string) @string</span><br><span class="line">#define confusion_CSTRING(string) string</span><br><span class="line">#endif</span><br></pre></td></tr></table></figure><p>其中decryptConstString定义在c文件GolobalCFile中<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line"> *  字符串混淆解密函数，将char[] 形式字符数组和 aa异或运算揭秘</span><br><span class="line"> *  如果没有经过混淆，请关闭宏开关</span><br><span class="line"> */</span><br><span class="line">extern char* decryptConstString(char* string)</span><br><span class="line">&#123;</span><br><span class="line">    char* origin_string = string;</span><br><span class="line">    while(*string) &#123;</span><br><span class="line">        *string ^= 0xAA;</span><br><span class="line">        string++;</span><br><span class="line">    &#125;</span><br><span class="line">    return origin_string;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>2.打开终端Terminal，cd到你的工程根目录；<br>3.执行如下混淆脚本（执行之前将脚本里面的工程名字改为你的工程名字）：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python .../confusion.py</span><br></pre></td></tr></table></figure></p><p>将如下脚本写在一个文件里confusion.py。在终端执行即可。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line">#!/usr/bin/env python</span><br><span class="line"># encoding=utf8</span><br><span class="line"># -*- coding: utf-8 -*-</span><br><span class="line"># 本脚本用于对源代码中的字符串进行加密</span><br><span class="line"># 替换所有字符串常量为加密的char数组，形式((char[])&#123;1, 2, 3, 0&#125;)</span><br><span class="line"></span><br><span class="line">import importlib</span><br><span class="line">import os</span><br><span class="line">import re</span><br><span class="line">import sys</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># 替换字符串为((char[])&#123;1, 2, 3, 0&#125;)的形式，同时让每个字节与0xAA异或进行加密</span><br><span class="line">def replace(match):</span><br><span class="line">    string = match.group(2) + &apos;\x00&apos;</span><br><span class="line">    replaced_string = &apos;((char []) &#123;&apos; + &apos;, &apos;.join([&quot;%i&quot; % ((ord© ^ 0xAA) if c != &apos;\0&apos; else 0) for c in list(string)]) + &apos;&#125;)&apos;</span><br><span class="line">    return match.group(1) + replaced_string + match.group(3)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># 修改源代码，加入字符串加密的函数</span><br><span class="line">def obfuscate(file):</span><br><span class="line">    with open(file, &apos;r&apos;) as f:</span><br><span class="line">        code = f.read()</span><br><span class="line">        f.close()</span><br><span class="line">        code = re.sub(r&apos;(confusion_NSSTRING\(|confusion_CSTRING\()&quot;(.*?)&quot;(\))&apos;, replace, code)</span><br><span class="line">        code = re.sub(r&apos;//#define ggh_confusion&apos;, &apos;#define ggh_confusion&apos;, code)</span><br><span class="line">        with open(file, &apos;w&apos;) as f:</span><br><span class="line">            f.write(code)</span><br><span class="line">            f.close()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#读取源码路径下的所有.h和.m 文件</span><br><span class="line">def openSrcFile(path):</span><br><span class="line">    print(&quot;开始处理路径： &quot;+ path +&quot;  下的所有.h和.m文件&quot;)</span><br><span class="line">    # this folder is custom</span><br><span class="line">    for parent,dirnames,filenames in os.walk(path):</span><br><span class="line">        #case 1:</span><br><span class="line">        #        for dirname in dirnames:</span><br><span class="line">        #            print((&quot; parent folder is:&quot; + parent).encode(&apos;utf-8&apos;))</span><br><span class="line">        #            print((&quot; dirname is:&quot; + dirname).encode(&apos;utf-8&apos;))</span><br><span class="line">        #case 2</span><br><span class="line">        for filename in filenames:</span><br><span class="line">            extendedName = os.path.splitext(os.path.join(parent,filename))</span><br><span class="line">            if (extendedName[1] == &apos;.h&apos; or extendedName[1] == &apos;.m&apos;):</span><br><span class="line">                print(&quot;处理源代码文件: &quot;+ os.path.join(parent,filename))</span><br><span class="line">                obfuscate(os.path.join(parent,filename))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#源码路径</span><br><span class="line">srcPath = &apos;../StringDecodeDemo&apos;</span><br><span class="line"></span><br><span class="line">if __name__ == &apos;__main__&apos;:</span><br><span class="line">    print(&quot;本脚本用于对源代码中被标记的字符串进行加密&quot;)</span><br><span class="line">    </span><br><span class="line">    if len(srcPath) &gt; 0:</span><br><span class="line">        openSrcFile(srcPath)</span><br><span class="line">    else:</span><br><span class="line">        print(&quot;请输入正确的源代码路径&quot;)</span><br><span class="line">        sys.exit()</span><br></pre></td></tr></table></figure></p><p>执行完成后查看你的代码，会发现用confusion_NSSTRING和confusion_CSTRING 写的明文字符串都被编码。这样就达到了混淆的效果。</p><p>4.由于代码被混淆不利于以后项目迭代，所以需要解码。方法同编码，在终端执行<br>如下脚本：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line">#!/usr/bin/env python</span><br><span class="line"># encoding=utf8</span><br><span class="line"># -*- coding: utf-8 -*-</span><br><span class="line"># 本脚本用于对源代码中的字符串进行解密</span><br><span class="line"># 替换所有加密的char数组为字符串常量，&quot;&quot;</span><br><span class="line"></span><br><span class="line">import importlib</span><br><span class="line">import os</span><br><span class="line">import re</span><br><span class="line">import sys</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># 替换((char[])&#123;1, 2, 3, 0&#125;)的形式为字符串，同时让每个数组值与0xAA异或进行解密</span><br><span class="line">def replace(match):</span><br><span class="line">    string = match.group(2)</span><br><span class="line">    decodeConfusion_string = &quot;&quot;</span><br><span class="line">    for numberStr in list(string.split(&apos;,&apos;)):</span><br><span class="line">        if int(numberStr) != 0:</span><br><span class="line">            decodeConfusion_string = decodeConfusion_string + &quot;%c&quot; % (int(numberStr) ^ 0xAA)</span><br><span class="line"></span><br><span class="line"># replaced_string = &apos;\&quot;&apos; + &quot;&quot;.join([&quot;%c&quot; % ((int© ^ 0xAA) if int© != 0 else &apos;\0&apos;) for c in string.split(&apos;,&apos;)]) + &apos;\&quot;&apos;</span><br><span class="line">    replaced_string = &apos;\&quot;&apos; + decodeConfusion_string + &apos;\&quot;&apos;</span><br><span class="line">    print(&quot;replaced_string = &quot; + replaced_string)</span><br><span class="line">    </span><br><span class="line">    return match.group(1) + replaced_string + match.group(3)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># 修改源代码，加入字符串加密的函数</span><br><span class="line">def obfuscate(file):</span><br><span class="line">    with open(file, &apos;r&apos;) as f:</span><br><span class="line">        code = f.read()</span><br><span class="line">        f.close()</span><br><span class="line">        code = re.sub(r&apos;(confusion_NSSTRING\(|confusion_CSTRING\()\(\(char \[\]\) \&#123;(.*?)\&#125;\)(\))&apos;, replace, code)</span><br><span class="line">        code = re.sub(r&apos;[/]*#define ggh_confusion&apos;, &apos;//#define ggh_confusion&apos;, code)</span><br><span class="line">        with open(file, &apos;w&apos;) as f:</span><br><span class="line">            f.write(code)</span><br><span class="line">            f.close()</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#读取源码路径下的所有.h和.m 文件</span><br><span class="line">def openSrcFile(path):</span><br><span class="line">    print(&quot;开始处理路径： &quot;+ path +&quot;  下的所有.h和.m文件&quot;)</span><br><span class="line">    # this folder is custom</span><br><span class="line">    for parent,dirnames,filenames in os.walk(path):</span><br><span class="line">        #case 1:</span><br><span class="line">        #        for dirname in dirnames:</span><br><span class="line">        #            print((&quot; parent folder is:&quot; + parent).encode(&apos;utf-8&apos;))</span><br><span class="line">        #            print((&quot; dirname is:&quot; + dirname).encode(&apos;utf-8&apos;))</span><br><span class="line">        #case 2</span><br><span class="line">        for filename in filenames:</span><br><span class="line">            extendedName = os.path.splitext(os.path.join(parent,filename))</span><br><span class="line">            #读取所有.h和.m 的源文件</span><br><span class="line">            if (extendedName[1] == &apos;.h&apos; or extendedName[1] == &apos;.m&apos;):</span><br><span class="line">                print(&quot;处理代码文件:&quot;+ os.path.join(parent,filename))</span><br><span class="line">                obfuscate(os.path.join(parent,filename))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#源码路径</span><br><span class="line">srcPath = &apos;../StringDecodeDemo&apos;</span><br><span class="line">if __name__ == &apos;__main__&apos;:</span><br><span class="line">    print(&quot;字符串解混淆脚本，将被标记过的char数组转为字符串，并和0xAA异或。还原代码&quot;)</span><br><span class="line">    if len(srcPath) &gt; 0:</span><br><span class="line">        openSrcFile(srcPath)</span><br><span class="line">    else:</span><br><span class="line">        print(&quot;请输入正确的源代码路径！&quot;)</span><br><span class="line">        sys.exit()</span><br></pre></td></tr></table></figure></p><p>即可解码明文。</p><p>为了不那么麻烦，可以打包前将工程拷贝一份，这样就可以只需要编码，不用解码。</p><p>以上脚本和代码均在文末Demo中。</p><h2 id="反编译"><a href="#反编译" class="headerlink" title="反编译"></a>反编译</h2><p><img src="http://pbznfv7sq.bkt.clouddn.com/8DD95EE6-2236-4BBF-886B-982214D0DBFB.png" alt="8DD95EE6-2236-4BBF-886B-982214D0DBFB"></p><p>在混淆了函数名以后，当然要检验一下成果了，这里就需要反编译我们的app了，有个很方便的工具<code>Class-dump</code>,这里有篇文章详细的描述了安装和使用方法：<a href="https://cnbin.github.io/blog/2015/05/21/objective-c-class-dump-an-zhuang-he-shi-yong-fang-fa/" target="_blank" rel="noopener">Objective-C Class-dump 安装和使用方法(原创)</a>.</p><p>另外还有一个反编译、反汇编和调试神器：Hopper。可以查看源码。</p><h2 id="疑问"><a href="#疑问" class="headerlink" title="疑问"></a>疑问</h2><p>1.执行脚本文件时报如下错误<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">confusion.py: Permission denied</span><br></pre></td></tr></table></figure></p><p>解决方法：<br>1、打开终端。<br>2、cd到目标文件夹。<br>3、输入 chmod 755 你的文件名.sh。</p><h2 id="Demo"><a href="#Demo" class="headerlink" title="Demo"></a>Demo</h2><p>本文demo：<a href="https://github.com/flowyears/iOS_CodeEncrypt" target="_blank" rel="noopener">iOS_CodeEncrypt</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;完整性校验&quot;&gt;&lt;a href=&quot;#完整性校验&quot; class=&quot;headerlink&quot; title=&quot;完整性校验&quot;&gt;&lt;/a&gt;完整性校验&lt;/h2&gt;&lt;p&gt;通过检测SignerIdentity判断是Mach-O文件否被篡改&lt;/p&gt;
&lt;p&gt;原理是：SignerIdentit
      
    
    </summary>
    
    
      <category term="iOS" scheme="https://flowyears.github.io/tags/iOS/"/>
    
  </entry>
  
  <entry>
    <title>自动化打包工具集Fastlnae</title>
    <link href="https://flowyears.github.io/2018/08/13/%E8%87%AA%E5%8A%A8%E5%8C%96%E6%89%93%E5%8C%85%E5%B7%A5%E5%85%B7%E9%9B%86Fastlane/"/>
    <id>https://flowyears.github.io/2018/08/13/自动化打包工具集Fastlane/</id>
    <published>2018-08-13T06:54:48.000Z</published>
    <updated>2018-08-13T07:00:16.918Z</updated>
    
    <content type="html"><![CDATA[<h2 id="什么是Fastlane"><a href="#什么是Fastlane" class="headerlink" title="什么是Fastlane"></a>什么是Fastlane</h2><blockquote><p>Fastlane是一套使用Ruby写的自动化工具集，旨在简化Android和iOS的部署过程，自动化你的工作流。它可以简化一些乏味、单调、重复的工作，像截图、代码签名以及发布App.</p></blockquote><h2 id="如何安装Fastlane"><a href="#如何安装Fastlane" class="headerlink" title="如何安装Fastlane"></a>如何安装Fastlane</h2><ol><li>安装xcode命令行工具<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">xcode-select --install</span><br></pre></td></tr></table></figure></li></ol><p>如果没有安装，会弹出对话框，点击安装;</p><p>如果已经安装,就会提示</p><blockquote><p>xcode-select: error: command line tools are already installed, use “Software Update” to install updates。</p></blockquote><ol start="2"><li>安装Fastlane</li></ol><p>使用下面的命令<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo gem install fastlane -NV</span><br></pre></td></tr></table></figure></p><p>或是<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew cask install fastlane</span><br></pre></td></tr></table></figure></p><p>来安装Fastlane。</p><p>安装完成后，可以执行下面命令，检查是否安装好。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fastlane --version</span><br></pre></td></tr></table></figure></p><ol start="3"><li>初始化Fastlane</li></ol><p>cd到你的项目目录执行<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fastlane init</span><br></pre></td></tr></table></figure></p><p>过一会会出现如下提示，让你选择一个选项：<br><img src="https://upload-images.jianshu.io/upload_images/760391-0bac65ce7d694006.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt=""></p><p>我这里希望打包上传到app store，所以选择了3.</p><p>如果你的工程是用cocoapods的那么可能会提示让你勾选工程的Scheme，步骤就是打开你的xcode，点击Manage Schemes，在一堆三方库中找到你的项目Scheme，在后面的多选框中进行勾选，然后rm -rf fastlane文件夹，重新fastlane init一下就不会报错了。</p><p><img src="https://upload-images.jianshu.io/upload_images/760391-7dd3dfbe86bcc3ba.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt=""></p><p>接着会提示你输入开发者账号和密码。</p><blockquote><p>[20:48:55]: Please enter your Apple ID developer credentials<br>[20:48:55]: Apple ID Username:</p></blockquote><p>登录成功后会提示你是否需要下载你的App的metadata。点y等待就可以。</p><p>初始化成功以后，就会生成一个如下图所示的fastlane文件夹：<br><img src="https://upload-images.jianshu.io/upload_images/760391-f42781c6443091c6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="fastlane"></p><p>其中<code>metadata</code>和<code>screenshots</code>分别对应App元数据和商店应用截图。</p><h4 id="Appfile"><a href="#Appfile" class="headerlink" title="Appfile"></a>Appfile</h4><p>Appfile用来存放app_identifier，apple_id和team_id。 它的格式是这样的：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">app_identifier &quot;com.xxx.xxx&quot; # app的bundle identifier</span><br><span class="line">apple_id &quot;xxx@xxx.com&quot; # 你的Apple ID</span><br><span class="line"> </span><br><span class="line">team_id &quot;XXXXXXXXXX&quot; # Team ID</span><br><span class="line">···</span><br></pre></td></tr></table></figure><p>你也可以为每个lane(后面会讲到)提供不同的 app_identifier, apple_id 和 team_id，例如：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">app_identifier &quot;com.aaa.aaa&quot;</span><br><span class="line">apple_id &quot;aaa@aaa.com&quot;</span><br><span class="line">team_id &quot;AAAAAAAAAA&quot;</span><br><span class="line"> </span><br><span class="line">for_lane : release do</span><br><span class="line">  app_identifier &quot;com.bbb.bbb&quot;</span><br><span class="line">  apple_id &quot;bbb@bbb.com&quot;</span><br><span class="line">  team_id &quot;AAAAAAAAAA&quot;</span><br><span class="line">end</span><br></pre></td></tr></table></figure><p>这里就是为Fastfile中定义的: release设置单独的信息。</p><h4 id="Deliverfile"><a href="#Deliverfile" class="headerlink" title="Deliverfile"></a>Deliverfile</h4><p><code>Deliverfile</code>中为发布的配置信息，一般情况用不到。</p><h4 id="Fastfile"><a href="#Fastfile" class="headerlink" title="Fastfile"></a>Fastfile</h4><p><code>Fastfile</code>是我们最应该关注的文件，也是我们的工作文件。<br>下面是我的fastfile文件中上传至app store的lane：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">platform :ios do</span><br><span class="line">#------------------------------APP STORE-----------------------------------</span><br><span class="line">desc &quot;Push a new release build to the App Store&quot;</span><br><span class="line">lane :to_appStore do</span><br><span class="line">#gym用来编译ipa</span><br><span class="line">gym(clean:true,# 编译前执行 clean，可减少 ipa 文件大小 </span><br><span class="line">scheme: &quot;MobileChecking&quot;,#要编译的scheme名称</span><br><span class="line">export_method: &quot;app-store&quot;, # Xcode 9增加export_method标签</span><br><span class="line">silent: true, # 隐藏没有必要的信息</span><br><span class="line">output_directory: &quot;./fastlane/appstoreIPA&quot;, # ipa输出目录</span><br><span class="line">output_name:&quot;mobileChecking&quot;, #输出的ipa名称</span><br><span class="line">archive_path:&quot;./fastlane/appstoreIpaArchive&quot;, #archive文件导出地址 </span><br><span class="line">export_xcargs: &quot;-allowProvisioningUpdates&quot;</span><br><span class="line">) </span><br><span class="line">upload_to_app_store</span><br><span class="line">end</span><br></pre></td></tr></table></figure></p><p>其中一个lane就是一个任务，里面是一个个的action组成的工作流。</p><p>export_method：指定打包所使用的输出方式,目前支持app-store, package, ad-hoc, enterprise, development, 和developer-id,即xcodebuild的。</p><h2 id="如何使用Fastlane"><a href="#如何使用Fastlane" class="headerlink" title="如何使用Fastlane"></a>如何使用Fastlane</h2><p>  定义完lane之后怎么执行呢？打开终端，切换到项目的根目录：执行fastlane [lane’name]就可以了。比如我的lane名称叫appStore，那么久执行如下命令：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fastlane  appStore</span><br></pre></td></tr></table></figure></p><p>或者采用下面的命令会更快：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bundle exec fastlane  appStore</span><br></pre></td></tr></table></figure><p>成功之后会在相应的路径下生成ipa文件,并会自动上传至app store。</p><h2 id="如何配置Fastfile"><a href="#如何配置Fastfile" class="headerlink" title="如何配置Fastfile"></a>如何配置Fastfile</h2><h2 id="上传到fir"><a href="#上传到fir" class="headerlink" title="上传到fir"></a>上传到fir</h2><p>####安装fir<br>使用如下命令，安装fir：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo gem install fir-cli</span><br></pre></td></tr></table></figure></p><p>安装完成以后，登录fir：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fir login</span><br></pre></td></tr></table></figure></p><p>会让你输入你的<code>fir.im API Token</code>,去fir网站即可获得此token。<br>安装fir：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fastlane add_plugin firim</span><br></pre></td></tr></table></figure></p><p>fir上传的lane：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">#-----------------------------------FIR-------------------------------</span><br><span class="line"></span><br><span class="line">desc &quot;Push a new release build to the FIR&quot;</span><br><span class="line">lane :to_fir do</span><br><span class="line">#build_app(workspace: &quot;MobileChecking.xcworkspace&quot;, scheme: &quot;MobileChecking&quot;)</span><br><span class="line">#gym用来编译ipa</span><br><span class="line">gym(clean: true,  # 编译前执行 clean，可减少 ipa 文件大小 </span><br><span class="line">scheme: &quot;MobileChecking&quot;, # 要编译的scheme名称</span><br><span class="line">export_method: &quot;ad-hoc&quot;, # Xcode 9增加export_method标签</span><br><span class="line">silent: true, # 隐藏没有必要的信息</span><br><span class="line">output_directory: &quot;./fastlane/firIPA&quot;, # ipa输出目录</span><br><span class="line">output_name:&quot;mobileChecking&quot;, # 输出的ipa名称</span><br><span class="line">archive_path:&quot;./fastlane/firIpaArchive&quot;, #archive文件导出地址 </span><br><span class="line">export_xcargs: &quot;-allowProvisioningUpdates&quot;, </span><br><span class="line">) </span><br><span class="line"># 上传ipa到fir.im服务器，在fir.im获取firim_api_token</span><br><span class="line">firim(firim_api_token: &quot;451d867c8860da31e5e46062b1ecea57&quot;)</span><br><span class="line"></span><br><span class="line">end</span><br></pre></td></tr></table></figure></p><p>如果你想上传到蒲公英，可以参考这篇文章：<a href="https://www.jianshu.com/p/6ef62c0415dc" target="_blank" rel="noopener">使用 fastlane 实现 iOS 持续集成（二）</a>。</p><h2 id="常见错误处理"><a href="#常见错误处理" class="headerlink" title="常见错误处理"></a>常见错误处理</h2><ol><li>FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">[14:57:59]: fastlane finished with errors</span><br><span class="line"></span><br><span class="line">[!] xcodebuild -showBuildSettings timed out after 4 retries with a </span><br><span class="line">base timeout of 3. You can override the base timeout value with the </span><br><span class="line">environment variable FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT, and the </span><br><span class="line">number of retries with the environment variable </span><br><span class="line">FASTLANE_XCODEBUILD_SETTINGS_RETRIES</span><br></pre></td></tr></table></figure></li></ol><p>在遇到这个错误之后，在命令窗口运行下面代码来更新timeout时间：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">#更新timeout</span><br><span class="line">export FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT=120</span><br></pre></td></tr></table></figure></p><p>参考</p><ul><li><a href="https://www.jianshu.com/p/5d836e89d9d1" target="_blank" rel="noopener">iOS效率神器fastlane自动打包</a></li><li><a href="https://zhuanlan.zhihu.com/p/23180455" target="_blank" rel="noopener">小团队的自动化发布－Fastlane带来的全自动化部署</a></li><li><a href="http://www.cocoachina.com/ios/20170519/19317.html" target="_blank" rel="noopener">iOS中使用Fastlane实现自动化打包和发布</a></li><li><a href="https://www.aliyun.com/jiaocheng/349851.html?spm=5176.100033.2.9.fBE7Ux" target="_blank" rel="noopener">当Fastlane遇到Xcode9打包出来不一定是ipa而是坑</a></li><li><a href="https://blog.csdn.net/yyh3663477/article/details/60468621" target="_blank" rel="noopener">Fastlane的gym指令</a></li><li><a href="https://www.jianshu.com/p/840943eff17b" target="_blank" rel="noopener">使用Fastlane实现iOS项目自动打包</a></li><li><a href="https://blog.csdn.net/cdut100/article/details/76381605" target="_blank" rel="noopener">(译)iOS自动化打包发布(fastlane)</a></li></ul>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;什么是Fastlane&quot;&gt;&lt;a href=&quot;#什么是Fastlane&quot; class=&quot;headerlink&quot; title=&quot;什么是Fastlane&quot;&gt;&lt;/a&gt;什么是Fastlane&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;Fastlane是一套使用Ruby写的自动
      
    
    </summary>
    
      <category term="工具   //在此处输入这篇文章的分类。" scheme="https://flowyears.github.io/categories/%E5%B7%A5%E5%85%B7-%E5%9C%A8%E6%AD%A4%E5%A4%84%E8%BE%93%E5%85%A5%E8%BF%99%E7%AF%87%E6%96%87%E7%AB%A0%E7%9A%84%E5%88%86%E7%B1%BB%E3%80%82/"/>
    
    
  </entry>
  
  <entry>
    <title>响应链之Hit-Testing</title>
    <link href="https://flowyears.github.io/2017/05/22/%E5%93%8D%E5%BA%94%E9%93%BE%E4%B9%8BHit-Testing/"/>
    <id>https://flowyears.github.io/2017/05/22/响应链之Hit-Testing/</id>
    <published>2017-05-22T13:06:00.000Z</published>
    <updated>2017-05-22T13:06:00.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="Hit-Testing-是什么"><a href="#Hit-Testing-是什么" class="headerlink" title="Hit-Testing 是什么"></a>Hit-Testing 是什么</h2><p>Hit-Testing 是一个决定一个点（比如一个触摸点）是否落在一个给定的物理对象上（比如绘制在屏幕上的UIView）的一个过程。</p><h2 id="Hit-Testing-执行时机"><a href="#Hit-Testing-执行时机" class="headerlink" title="Hit-Testing 执行时机"></a>Hit-Testing 执行时机</h2><p>Hit-Testing是在每次手指触摸时执行的。并且是在任何视图或者手势收到UIEvent（代表触摸属于的事件）之前。</p><h2 id="Hit-Testing-的实现"><a href="#Hit-Testing-的实现" class="headerlink" title="Hit-Testing 的实现"></a>Hit-Testing 的实现</h2><p>实现：Hit-Testing采用深度优先的反序访问迭代算法（先访问根节点然后从高到低访（从离用户近的视图或者说是后添加的视图）问低节点）。这种遍历方法可以减少遍历迭代的次数。</p><p>结束条件：<strong>一旦找到最深的包含触摸点的后裔视图就停止遍历</strong>（注意，是最深的）。</p><p>下面举例说明：</p><p><img src="http://upload-images.jianshu.io/upload_images/760391-3249f23ee5246a75.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="图1"></p><p>如上图所示，视图A\B\C依次添加到视图上，比如View B的添加比A晚比C早，而自视图B.1比B.2添加的要早。</p><p><img src="http://upload-images.jianshu.io/upload_images/760391-241b32615852e167.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="图2"></p><p>通过深度优先的反向遍历允许一旦找到第一个最深的后裔包含触摸点的视图就停止遍历，如上图所示，找到B.1后就停止，不会继续遍历A视图。</p><p>遍历算法以向<code>UIWindow</code>（视图层次结构的根视图）发送<code>hitTest:withEvent:</code>消息<strong>开始</strong>。这个方法返回的值就是包含触摸点的最前面的视图。<br>下面流程图说明了<code>hit-test</code>逻辑:</p><p><img src="http://upload-images.jianshu.io/upload_images/760391-28b3f924086a454b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="图3"></p><p>下面的代码展示了原生<code>hitTest:withEvent:</code>可能的实现：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event &#123;</span><br><span class="line">    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha &lt;= 0.01) &#123;</span><br><span class="line">        return nil;</span><br><span class="line">    &#125;</span><br><span class="line">    if ([self pointInside:point withEvent:event]) &#123;</span><br><span class="line">        for (UIView *subview in [self.subviews reverseObjectEnumerator]) &#123;</span><br><span class="line">            CGPoint convertedPoint = [subview convertPoint:point fromView:self];</span><br><span class="line">            UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];</span><br><span class="line">            if (hitTestView) &#123;</span><br><span class="line">                return hitTestView;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        return self;</span><br><span class="line">    &#125;</span><br><span class="line">    return nil;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>hitTest:withEvent:</code>方法首先检查视图是否允许接收触摸事件。视图允许接收触摸事件的四个条件是：</p><ul><li>视图不是隐藏的: self.hidden == NO</li><li>视图是允许交互的: self.userInteractionEnabled == YES</li><li>视图透明度大于0.01: self.alpha &gt; 0.01</li><li>视图包含这个点: pointInside:withEvent: == YES</li></ul><p>图2遍历顺序为：UIWindow-&gt;MainView-&gt;View C-&gt;ViewB-&gt;View B.2-&gt;View B.1.</p><p>解释一下为什么顺序是这样：</p><ol><li>首先遍历UIWindow，然后MainView</li><li>MainView 有三个子视图ABC，根据算法描述中所讲，首先遍历离用户最近的视图，所以，先遍历View C</li><li>每次遍历时需要判断接收触摸的四个条件，由于落点不在C中，所以在hitTest遍历C时返回nil，然后继续遍历View B,</li><li>然后遍历View B的两个子视图，与第2点判断条件一样，先遍历View B.2</li><li>由于落点不在B.2中，所以继续遍历B.1,由于此时满足结束条件，即接收触摸事件并且B.1没有子视图，遍历到此结束。</li></ol><h2 id="覆盖hitTest-withEvent-的一些用途"><a href="#覆盖hitTest-withEvent-的一些用途" class="headerlink" title="覆盖hitTest:withEvent:的一些用途"></a>覆盖hitTest:withEvent:的一些用途</h2><p><code>hitTest:withEvent:</code>可以被覆盖,那么覆盖它可以做些什么呢？</p><h3 id="1-增加视图的触摸区域"><a href="#1-增加视图的触摸区域" class="headerlink" title="1.增加视图的触摸区域"></a>1.增加视图的触摸区域</h3><p><img src="http://upload-images.jianshu.io/upload_images/760391-220f9865d5a14335.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="hit-test-increase-touch-area.png"></p><p>如上图所示，蓝色按钮太小，如果采用设置UIButton的image来放大点击区域，调整按钮坐标的代码就很不好看了，如果用覆盖<code>hitTest:withEvent:</code>的方法来解决这个方法，就要优雅一些，自定义UIButton,覆盖hit-testing方法，代码如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event &#123;</span><br><span class="line">    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha &lt;= 0.01) &#123;</span><br><span class="line">        return nil;</span><br><span class="line">    &#125;</span><br><span class="line">    CGRect touchRect = CGRectInset(self.bounds, -10, -10);</span><br><span class="line">    if (CGRectContainsPoint(touchRect, point)) &#123;</span><br><span class="line">        for (UIView *subview in [self.subviews reverseObjectEnumerator]) &#123;</span><br><span class="line">            CGPoint convertedPoint = [subview convertPoint:point fromView:self];</span><br><span class="line">            UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];</span><br><span class="line">            if (hitTestView) &#123;</span><br><span class="line">                return hitTestView;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        return self;</span><br><span class="line">    &#125;</span><br><span class="line">    return nil;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-传递触摸事件给父视图"><a href="#2-传递触摸事件给父视图" class="headerlink" title="2.传递触摸事件给父视图"></a>2.传递触摸事件给父视图</h3><p>有的时候对于一个视图忽略触摸事件并传递给下面的视图是很重要的。例如，假设一个透明的视图覆盖在应用内所有视图的最上面。覆盖层有子视图应该相应触摸事件的一些控件和按钮。但是触摸覆盖层的其他区域应该传递给覆盖层下面的视图。为了完成这个行为，覆盖层需要覆盖hitTest:withEvent:方法来返回包含触摸点的子视图中的一个，然后其他情况返回nil，包括覆盖层包含触摸点的情况：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event &#123;</span><br><span class="line">    UIView *hitTestView = [super hitTest:point withEvent:event];</span><br><span class="line">    if (hitTestView == self) &#123;</span><br><span class="line">        hitTestView = nil;</span><br><span class="line">    &#125;</span><br><span class="line">    return hitTestView;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-传递触摸事件给子视图"><a href="#3-传递触摸事件给子视图" class="headerlink" title="3.传递触摸事件给子视图"></a>3.传递触摸事件给子视图</h3><p><img src="http://upload-images.jianshu.io/upload_images/760391-977751130665be15.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="hit-test-pass-touches-to-subviews.png"></p><p>蓝色方框是一个图片浏览器，在蓝色方框内滑动，可以翻动图片，但是在方框之外是无法响应的，因为手指落点不在图片浏览器的bounces里面，那么如何让手指落在上图位置时，也可以滚动图片呢？方法是在图片浏览器的父视图中，重载<code>hitTest:withEvent:</code>方法，当触摸到图片浏览器自视图之外的视图时，返回图片浏览器即可：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event &#123;</span><br><span class="line">    UIView *hitTestView = [super hitTest:point withEvent:event];</span><br><span class="line">    if (hitTestView) &#123;</span><br><span class="line">        hitTestView = self.scrollView;</span><br><span class="line">    &#125;</span><br><span class="line">    return hitTestView;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-响应子view超出了父view的bounds事件"><a href="#4-响应子view超出了父view的bounds事件" class="headerlink" title="4.响应子view超出了父view的bounds事件"></a>4.响应子view超出了父view的bounds事件</h3><p>比如自定义Tabbar中间的大按钮，点击超出Tabbar bounds的区域也需要响应，此时重载父view的<code>hitTest: withEvent:</code>方法，<strong>去掉点击必须在父view内的判断</strong>，然后子view就能成为 hit-test view用于响应事件了。<a href="http://www.jianshu.com/p/3126db96e8d3" target="_blank" rel="noopener">这篇文章</a>详细的描述了如何实现。</p><p>参考：<a href="http://joywii.github.io/blog/2015/03/17/ioszhong-de-hit-testing/" target="_blank" rel="noopener">iOS中的Hit-Testing</a></p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;h2 id=&quot;Hit-Testing-是什么&quot;&gt;&lt;a href=&quot;#Hit-Testing-是什么&quot; class=&quot;headerlink&quot; title=&quot;Hit-Testing 是什么&quot;&gt;&lt;/a&gt;Hit-Testing 是什么&lt;/h2&gt;&lt;p&gt;Hit-Testing 是一个决定一
      
    
    </summary>
    
      <category term="响应链   //在此处输入这篇文章的分类。" scheme="https://flowyears.github.io/categories/%E5%93%8D%E5%BA%94%E9%93%BE-%E5%9C%A8%E6%AD%A4%E5%A4%84%E8%BE%93%E5%85%A5%E8%BF%99%E7%AF%87%E6%96%87%E7%AB%A0%E7%9A%84%E5%88%86%E7%B1%BB%E3%80%82/"/>
    
    
  </entry>
  
  <entry>
    <title>UITabBar 自定义中间大按钮</title>
    <link href="https://flowyears.github.io/2017/05/22/UITabBar%20%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%AD%E9%97%B4%E5%A4%A7%E6%8C%89%E9%92%AE/"/>
    <id>https://flowyears.github.io/2017/05/22/UITabBar 自定义中间大按钮/</id>
    <published>2017-05-22T13:02:20.000Z</published>
    <updated>2017-05-22T13:02:20.000Z</updated>
    
    <content type="html"><![CDATA[<p>在项目中经常会有这种需求：在tabBar的正中间放置一个大按钮，有时候会超出tabBar的可点击范围。如下图的闲鱼：</p><p><img src="http://upload-images.jianshu.io/upload_images/760391-c8711365e3606a12.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="闲鱼TabBar"></p><p>主要思路就是：</p><ul><li>自定义UITabBarController</li><li>自定义UITabBar</li><li>在自定义的UITabBarController中用自定义的UITabBar替换掉原有的tabbar。</li></ul><p>首先，利用KVC在自定义的UITabBarController中用自定义的UITabBar替换掉原有的tabbar：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">CustomTabBar *myTabBar = [[CustomTabBar alloc] init];</span><br><span class="line">[self setValue:myTabBar forKey:@&quot;tabBar&quot;];</span><br></pre></td></tr></table></figure><p>大按钮好解决，用一张大点的图片即可。但是你会发现点击超出tabbar顶部的部分是不能响应的。如果你知道hit-test的工作原理，就会知道为什么超出部分不能响应。响应的四个条件是：</p><ul><li>视图不是隐藏的: self.hidden == NO</li><li>视图是允许交互的: self.userInteractionEnabled == YES</li><li>视图透明度大于0.01: self.alpha &gt; 0.01</li><li>视图包含这个点: pointInside:withEvent: == YES<br>在这里就是由于不满足第四个条件，即点击的point没有落在父视图的bounce之内，所以无法响应。</li></ul><p>系统的hit-test方法实现大概如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event</span><br><span class="line">&#123;</span><br><span class="line">    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha &lt;= 0.01)</span><br><span class="line">    &#123;</span><br><span class="line">        return nil;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    if ([self pointInside:point withEvent:event])</span><br><span class="line">    &#123;</span><br><span class="line">        for (UIView *subview in [self.subviews reverseObjectEnumerator])</span><br><span class="line">        &#123;</span><br><span class="line">            CGPoint convertedPoint = [subview convertPoint:point fromView:self];</span><br><span class="line">            UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];</span><br><span class="line">            if (hitTestView)</span><br><span class="line">            &#123;</span><br><span class="line">                return hitTestView;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    return nil;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通常解决这个问题的做法是将响应区域扩大，这个我还没有研究。</p><p>不过我的思路特殊一点：在自定义的UITabBar中重写hit-test方法，将点击的point进行修改，让它落在可点击范围之内。</p><p>由于是点击的point的y坐标超出了tabbar范围，那么只要修改这个y坐标，让他落在tabbar的可点击范围内即可。</p><p>修改y坐标的代码如下所示：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event</span><br><span class="line">&#123;</span><br><span class="line">    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha &lt;= 0.01)</span><br><span class="line">    &#123;</span><br><span class="line">        return nil;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    if ([self pointInside:point withEvent:event])</span><br><span class="line">    &#123;</span><br><span class="line">        return [self mmHitTest:point withEvent:event];</span><br><span class="line">    &#125;</span><br><span class="line">    else</span><br><span class="line">    &#123;</span><br><span class="line">        CGPoint otherPoint = CGPointMake(point.x, point.y + self.effectAreaY);</span><br><span class="line">        return [self mmHitTest:otherPoint withEvent:event];</span><br><span class="line">    &#125;</span><br><span class="line">    return nil;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (UIView *)mmHitTest:(CGPoint)point withEvent:(UIEvent *)event</span><br><span class="line">&#123;</span><br><span class="line">    for (UIView *subview in [self.subviews reverseObjectEnumerator])</span><br><span class="line">    &#123;</span><br><span class="line">        CGPoint convertedPoint = [subview convertPoint:point fromView:self];</span><br><span class="line">        UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];</span><br><span class="line">        if (hitTestView) &#123;</span><br><span class="line">            return hitTestView;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    return nil;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其中effectAreaY是超出tabbar的垂直距离，比如大按钮顶部超出20，那么这个值就是20，你可以根据需求进行调整。</p><p>到这里，超出tabbar区域的大按钮点击问题就得到解决了，但是我反复点了一下，发现一个小问题，就是每个tabbar的item超出部分都能点击，这是我们不想要的。</p><p>我们想要的是落点在中间按钮范围内时才去修改这个落点，所以，加一个判断即可，只要点击的point的x坐标在中间大按钮范围之内就去修改，代码如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event</span><br><span class="line">&#123;</span><br><span class="line">    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha &lt;= 0.01)</span><br><span class="line">    &#123;</span><br><span class="line">        return nil;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    if ([self pointInside:point withEvent:event])</span><br><span class="line">    &#123;</span><br><span class="line">        return [self mmHitTest:point withEvent:event];</span><br><span class="line">    &#125;</span><br><span class="line">    else</span><br><span class="line">    &#123;</span><br><span class="line">        CGFloat tabBarItemWidth = self.bounds.size.width/self.items.count;</span><br><span class="line">        CGFloat left = self.center.x - tabBarItemWidth/2;</span><br><span class="line">        CGFloat right = self.center.x + tabBarItemWidth/2;</span><br><span class="line">        </span><br><span class="line">        if (point.x &lt; right &amp;&amp;</span><br><span class="line">            point.x &gt; left)</span><br><span class="line">        &#123;//当点击的point的x坐标是中间item范围内，才去修正落点</span><br><span class="line">            CGPoint otherPoint = CGPointMake(point.x, point.y + self.effectAreaY);</span><br><span class="line">            return [self mmHitTest:otherPoint withEvent:event];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    return nil;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (UIView *)mmHitTest:(CGPoint)point withEvent:(UIEvent *)event</span><br><span class="line">&#123;</span><br><span class="line">    for (UIView *subview in [self.subviews reverseObjectEnumerator])</span><br><span class="line">    &#123;</span><br><span class="line">        CGPoint convertedPoint = [subview convertPoint:point fromView:self];</span><br><span class="line">        UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];</span><br><span class="line">        if (hitTestView) &#123;</span><br><span class="line">            return hitTestView;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    return nil;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>另外，如果想调整图片和文字位置，调整tabbarItem的属性：<br> <figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[vc.tabBarItem setImageInsets:UIEdgeInsetsMake(-30, 0, 30, 0)];//修改图片偏移量，上下，左右必须为相反数，否则图片会被压缩</span><br><span class="line">[vc.tabBarItem setTitlePositionAdjustment:UIOffsetMake(0, -30)];//修改文字偏移量</span><br></pre></td></tr></table></figure></p><p>vc是每个根控制器。</p><p>到这里，就完美的解决了自定义大按钮及其点击问题，相当简单。</p><p>总的来说要点就是：</p><ul><li>替换系统的UITabBar</li><li>修改点击point的落点</li><li>判断什么时候才去修改落点。</li></ul><p>最后附上Demo：<a href="https://github.com/flowyears/HitTesting" target="_blank" rel="noopener">HitTesting</a></p><p>end~ 如果觉得有用，请点个赞👍，谢谢~</p>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;在项目中经常会有这种需求：在tabBar的正中间放置一个大按钮，有时候会超出tabBar的可点击范围。如下图的闲鱼：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://upload-images.jianshu.io/upload_images/760391-c8711365
      
    
    </summary>
    
      <category term="UI   //在此处输入这篇文章的分类。" scheme="https://flowyears.github.io/categories/UI-%E5%9C%A8%E6%AD%A4%E5%A4%84%E8%BE%93%E5%85%A5%E8%BF%99%E7%AF%87%E6%96%87%E7%AB%A0%E7%9A%84%E5%88%86%E7%B1%BB%E3%80%82/"/>
    
    
  </entry>
  
  <entry>
    <title>iOS常用开源库</title>
    <link href="https://flowyears.github.io/2017/04/09/iOS%E5%B8%B8%E7%94%A8%E5%BC%80%E6%BA%90%E5%BA%93/"/>
    <id>https://flowyears.github.io/2017/04/09/iOS常用开源库/</id>
    <published>2017-04-09T00:55:29.000Z</published>
    <updated>2017-04-09T13:49:58.000Z</updated>
    
    <content type="html"><![CDATA[<h2 id="❋Objective-C库❋"><a href="#❋Objective-C库❋" class="headerlink" title="❋Objective-C库❋"></a>❋Objective-C库❋</h2><h3 id="网络请求"><a href="#网络请求" class="headerlink" title="网络请求"></a>网络请求</h3><h4 id="1-AFNetworking-🔥"><a href="#1-AFNetworking-🔥" class="headerlink" title="1. AFNetworking  🔥"></a>1. <a href="https://github.com/AFNetworking/AFNetworking.git" target="_blank" rel="noopener">AFNetworking  🔥</a></h4><h3 id="浏览器"><a href="#浏览器" class="headerlink" title="浏览器"></a>浏览器</h3><h4 id="1-SVWebViewController-🔥"><a href="#1-SVWebViewController-🔥" class="headerlink" title="1. SVWebViewController  🔥"></a>1. <a href="https://github.com/TransitApp/SVWebViewController" target="_blank" rel="noopener">SVWebViewController  🔥</a></h4><a id="more"></a><h3 id="数据库"><a href="#数据库" class="headerlink" title="数据库"></a>数据库</h3><h4 id="1-FMDB-🔥"><a href="#1-FMDB-🔥" class="headerlink" title="1. FMDB  🔥"></a>1. <a href="https://github.com/ccgus/fmdb.git" target="_blank" rel="noopener">FMDB  🔥</a></h4><h4 id="2-MagicalRecord"><a href="#2-MagicalRecord" class="headerlink" title="2. MagicalRecord"></a>2. <a href="https://github.com/magicalpanda/MagicalRecord" target="_blank" rel="noopener">MagicalRecord</a></h4><p> CoreData</p><h3 id="自动布局"><a href="#自动布局" class="headerlink" title="自动布局"></a>自动布局</h3><h4 id="1-Masonry-🔥"><a href="#1-Masonry-🔥" class="headerlink" title="1.  Masonry 🔥"></a>1.  <a href="https://github.com/cloudkite/Masonry.git" target="_blank" rel="noopener">Masonry 🔥</a></h4><h3 id="图表"><a href="#图表" class="headerlink" title="图表"></a>图表</h3><h4 id="1-Charts"><a href="#1-Charts" class="headerlink" title="1. Charts"></a>1. <a href="https://github.com/danielgindi/Charts" target="_blank" rel="noopener">Charts</a></h4><h3 id="Time-Date"><a href="#Time-Date" class="headerlink" title="Time | Date"></a>Time | Date</h3><h4 id="1-DateTools"><a href="#1-DateTools" class="headerlink" title="1. DateTools"></a>1. <a href="https://github.com/MatthewYork/DateTools" target="_blank" rel="noopener">DateTools</a></h4><p>时间转友好化描述</p><h3 id="富文本"><a href="#富文本" class="headerlink" title="富文本"></a>富文本</h3><h4 id="1-TTTAttributedLabel-🔥"><a href="#1-TTTAttributedLabel-🔥" class="headerlink" title="1. TTTAttributedLabel  🔥"></a>1. <a href="https://github.com/TTTAttributedLabel/TTTAttributedLabel" target="_blank" rel="noopener">TTTAttributedLabel  🔥</a></h4><h4 id="2-YYText-🔥"><a href="#2-YYText-🔥" class="headerlink" title="2. YYText  🔥"></a>2. <a href="https://github.com/ibireme/YYText" target="_blank" rel="noopener">YYText  🔥</a></h4><h3 id="动画"><a href="#动画" class="headerlink" title="动画"></a>动画</h3><h4 id="1-JazzHands"><a href="#1-JazzHands" class="headerlink" title="1. JazzHands"></a>1. <a href="https://github.com/IFTTT/JazzHands" target="_blank" rel="noopener">JazzHands</a></h4><p>基于keyframe的UIKit动画框架（coding-iOS有用到)</p><h4 id="2-FeSpinner"><a href="#2-FeSpinner" class="headerlink" title="2. FeSpinner"></a>2. <a href="https://github.com/NghiaTranUIT/FeSpinner" target="_blank" rel="noopener">FeSpinner</a></h4><p>多种启动画面动画效果<br><img src="https://camo.githubusercontent.com/efc8fa347b8d0e3b8054a9ee240364562a586e41/687474703a2f2f6e676869617472616e2e6d652f77702d636f6e74656e742f75706c6f6164732f323031352f30312f48616e6477726974696e672e676966" alt=""></p><p><img src="https://camo.githubusercontent.com/922f1a7efb519cfbfe0c8c5c68b2bb2f69a2c744/687474703a2f2f6e676869617472616e2e6d652f77702d636f6e74656e742f75706c6f6164732f323031352f30312f457175616c697a65724c6f616465722e676966" alt=""></p><p><img src="https://camo.githubusercontent.com/310f0bac2d9c078ff6c7695ac9c97aba6ab9f31e/687474703a2f2f6e676869617472616e2e6d652f77702d636f6e74656e742f75706c6f6164732f323031352f30312f486f7572476c6173734c6f616465722e676966" alt=""></p><h4 id="3-PulsingHalo"><a href="#3-PulsingHalo" class="headerlink" title="3. PulsingHalo"></a>3. <a href="https://github.com/shu223/PulsingHalo" target="_blank" rel="noopener">PulsingHalo</a></h4><p>涟漪动画效果<br><img src="https://github.com/shu223/PulsingHalo/raw/master/demo_.gif" alt=""></p><h3 id="图片相关"><a href="#图片相关" class="headerlink" title="图片相关"></a>图片相关</h3><h4 id="1-SDWebImage-🔥"><a href="#1-SDWebImage-🔥" class="headerlink" title="1. SDWebImage 🔥"></a>1. <a href="https://github.com/rs/SDWebImage.git" target="_blank" rel="noopener">SDWebImage 🔥</a></h4><p>加载网络图片</p><h4 id="2-GPUImage-🔥"><a href="#2-GPUImage-🔥" class="headerlink" title="2. GPUImage  🔥"></a>2. <a href="https://github.com/BradLarson/GPUImage" target="_blank" rel="noopener">GPUImage  🔥</a></h4><p>图像处理 </p><h4 id="3-MWPhotoBrowser-🔥"><a href="#3-MWPhotoBrowser-🔥" class="headerlink" title="3. MWPhotoBrowser  🔥"></a>3. <a href="https://github.com/mwaterfall/MWPhotoBrowser.git" target="_blank" rel="noopener">MWPhotoBrowser  🔥</a></h4><p>浏览图片</p><h4 id="4-CTAssetsPickerController"><a href="#4-CTAssetsPickerController" class="headerlink" title="4. CTAssetsPickerController"></a>4. <a href="https://github.com/chiunam/CTAssetsPickerController" target="_blank" rel="noopener">CTAssetsPickerController</a></h4><p>图片选择器</p><h4 id="5-TDImageColors"><a href="#5-TDImageColors" class="headerlink" title="5. TDImageColors"></a>5. <a href="https://github.com/timominous/TDImageColors" target="_blank" rel="noopener">TDImageColors</a></h4><p>检测出图片上使用最多的颜色</p><h4 id="6-QBImagePicker"><a href="#6-QBImagePicker" class="headerlink" title="6. QBImagePicker"></a>6. <a href="https://github.com/questbeat/QBImagePicker" target="_blank" rel="noopener">QBImagePicker</a></h4><p>star 1400+</p><h4 id="7-TOCropViewController"><a href="#7-TOCropViewController" class="headerlink" title="7. TOCropViewController"></a>7. <a href="https://github.com/TimOliver/TOCropViewController" target="_blank" rel="noopener">TOCropViewController</a></h4><p>图片剪裁，旋转等</p><h3 id="Model"><a href="#Model" class="headerlink" title="Model"></a>Model</h3><h4 id="1-Mantle"><a href="#1-Mantle" class="headerlink" title="1. Mantle"></a>1. <a href="https://github.com/Mantle/Mantle" target="_blank" rel="noopener">Mantle</a></h4><p>json转model</p><h4 id="2-YYKit-🔥"><a href="#2-YYKit-🔥" class="headerlink" title="2. YYKit  🔥"></a>2. <a href="https://github.com/ibireme/YYKit" target="_blank" rel="noopener">YYKit  🔥</a></h4><h3 id="颜色Color-amp-素材"><a href="#颜色Color-amp-素材" class="headerlink" title="颜色Color &amp; 素材"></a>颜色Color &amp; 素材</h3><h4 id="1-Chameleon-🔥"><a href="#1-Chameleon-🔥" class="headerlink" title="1. Chameleon  🔥"></a>1. <a href="https://github.com/ViccAlexander/Chameleon" target="_blank" rel="noopener">Chameleon  🔥</a></h4><p>一个iOS的色彩框架。它运用现代化flat color将UIColor扩展地非常美观。我们还可以通过它运用自定义颜色创建调色板(OC&amp;sWIFT)</p><h4 id="2-FontAwesomeKit-🔥"><a href="#2-FontAwesomeKit-🔥" class="headerlink" title="2. FontAwesomeKit 🔥"></a>2. <a href="https://github.com/PrideChung/FontAwesomeKit" target="_blank" rel="noopener">FontAwesomeKit 🔥</a></h4><p>Font Awesome 是一款可缩放的矢量图标字库，你可以利用CSS将Font Awesome发挥到极致，包括：大小、颜色、阴影或者其它任何支持的效果</p><h3 id="侧边栏"><a href="#侧边栏" class="headerlink" title="侧边栏"></a>侧边栏</h3><h4 id="1-MMDrawerController"><a href="#1-MMDrawerController" class="headerlink" title="1. MMDrawerController"></a>1. <a href="https://github.com/mutualmobile/MMDrawerController" target="_blank" rel="noopener">MMDrawerController</a></h4><h4 id="2-RESideMenu"><a href="#2-RESideMenu" class="headerlink" title="2. RESideMenu"></a>2. <a href="https://github.com/romaonthego/RESideMenu" target="_blank" rel="noopener">RESideMenu</a></h4><p>侧边栏，类似QQ</p><h4 id="3-PKRevealController"><a href="#3-PKRevealController" class="headerlink" title="3. PKRevealController"></a>3. <a href="https://github.com/pkluz/PKRevealController" target="_blank" rel="noopener">PKRevealController</a></h4><h3 id="TextField-amp-TextView"><a href="#TextField-amp-TextView" class="headerlink" title="TextField &amp; TextView"></a>TextField &amp; TextView</h3><h4 id="1-JVFloatLabeledTextField-🔥"><a href="#1-JVFloatLabeledTextField-🔥" class="headerlink" title="1. JVFloatLabeledTextField 🔥"></a>1. <a href="https://github.com/jverdi/JVFloatLabeledTextField" target="_blank" rel="noopener">JVFloatLabeledTextField 🔥</a></h4><p>浮动式文本输入框</p><h4 id="2-TextFieldEffects"><a href="#2-TextFieldEffects" class="headerlink" title="2. TextFieldEffects"></a>2. <a href="https://github.com/raulriera/TextFieldEffects" target="_blank" rel="noopener">TextFieldEffects</a></h4><p>浮动式文本输入框</p><h4 id="3-SlackTextViewController"><a href="#3-SlackTextViewController" class="headerlink" title="3. SlackTextViewController"></a>3. <a href="https://github.com/slackhq/SlackTextViewController" target="_blank" rel="noopener">SlackTextViewController</a></h4><p>是功能强大易用的TableView和CollectionView下的文字输入解决方案。具有文字输入框高度自适应，自动输入，复制单元格内容等诸多好用的特色 ☆6700+</p><h3 id="提示进度条"><a href="#提示进度条" class="headerlink" title="提示进度条"></a>提示进度条</h3><h4 id="1-MBProgressHUD-🔥�"><a href="#1-MBProgressHUD-🔥�" class="headerlink" title="1. MBProgressHUD 🔥�"></a>1. <a href="https://github.com/matej/MBProgressHUD" target="_blank" rel="noopener">MBProgressHUD 🔥�</a></h4><p>指示器(菊花，风火轮) </p><h4 id="2-DACircularProgress"><a href="#2-DACircularProgress" class="headerlink" title="2. DACircularProgress"></a>2. <a href="https://github.com/danielamitay/DACircularProgress" target="_blank" rel="noopener">DACircularProgress</a></h4><p>环形进度条</p><h4 id="3-CRToast-🔥"><a href="#3-CRToast-🔥" class="headerlink" title="3. CRToast 🔥"></a>3. <a href="https://github.com/cruffenach/CRToast" target="_blank" rel="noopener">CRToast 🔥</a></h4><p>状态栏弹出消息提醒<br><img src="https://github.com/cruffenach/CRToast/raw/master/screenshots/demo.gif" alt=""></p><h4 id="4-RKDropdownAlert"><a href="#4-RKDropdownAlert" class="headerlink" title="4. RKDropdownAlert"></a>4. <a href="https://github.com/cwRichardKim/RKDropdownAlert" target="_blank" rel="noopener">RKDropdownAlert</a></h4><p>顶部通知提示</p><h4 id="5-JDStatusBarNotification"><a href="#5-JDStatusBarNotification" class="headerlink" title="5. JDStatusBarNotification"></a>5. <a href="https://github.com/jaydee3/JDStatusBarNotification" target="_blank" rel="noopener">JDStatusBarNotification</a></h4><p>status bar指示条</p><h4 id="6-M13ProgressSuite"><a href="#6-M13ProgressSuite" class="headerlink" title="6. M13ProgressSuite"></a>6. <a href="https://github.com/Marxon13/M13ProgressSuite" target="_blank" rel="noopener">M13ProgressSuite</a></h4><p>进度条☆3000,种类很多<br><img src="https://camo.githubusercontent.com/09f2c629a458564176f6096db0be1a69b8eaad92/68747470733a2f2f7261772e6769746875622e636f6d2f4d6172786f6e31332f4d313350726f677265737353756974652f6d61737465722f526561646d655265736f75726365732f55494e617669676174696f6e4261722e676966" alt=""></p><p><img src="https://camo.githubusercontent.com/f6b0c2077abdc66f550a7e9ff383a359a7145708/68747470733a2f2f7261772e6769746875622e636f6d2f4d6172786f6e31332f4d313350726f677265737353756974652f6d61737465722f526561646d655265736f75726365732f48554442617369632e676966" alt=""></p><h3 id="键盘"><a href="#键盘" class="headerlink" title="键盘"></a>键盘</h3><h4 id="1-IQKeyboardManager"><a href="#1-IQKeyboardManager" class="headerlink" title="1. IQKeyboardManager"></a>1. <a href="https://github.com/hackiftekhar/IQKeyboardManager.git" target="_blank" rel="noopener">IQKeyboardManager</a></h4><p>解决键盘在遮挡(OC &amp; sSwift) </p><h3 id="引导页-amp-启动页"><a href="#引导页-amp-启动页" class="headerlink" title="引导页 &amp; 启动页"></a>引导页 &amp; 启动页</h3><h4 id="1-Onboard-🔥"><a href="#1-Onboard-🔥" class="headerlink" title="1. Onboard 🔥"></a>1. <a href="https://github.com/mamaral/Onboard" target="_blank" rel="noopener">Onboard 🔥</a></h4><p>引导页(oc &amp; swift)<br><img src="https://github.com/mamaral/Onboard/raw/master/Screenshots/city.gif" alt=""><img src="https://github.com/mamaral/Onboard/raw/master/Screenshots/almanac.gif" alt=""></p><h3 id="卡片动画"><a href="#卡片动画" class="headerlink" title="卡片动画"></a>卡片动画</h3><h4 id="1-iCarousel"><a href="#1-iCarousel" class="headerlink" title="1. iCarousel"></a>1. <a href="https://github.com/nicklockwood/iCarousel" target="_blank" rel="noopener">iCarousel</a></h4><p>很炫的视图切换，cover follow</p><h4 id="2-ZLSwipeableView"><a href="#2-ZLSwipeableView" class="headerlink" title="2.  ZLSwipeableView"></a>2.  <a href="https://github.com/zhxnlai/ZLSwipeableView" target="_blank" rel="noopener">ZLSwipeableView</a></h4><p>卡片动画,左右滑动消失</p><h4 id="3-TinderSimpleSwipeCards"><a href="#3-TinderSimpleSwipeCards" class="headerlink" title="3. TinderSimpleSwipeCards"></a>3. <a href="https://github.com/cwRichardKim/TinderSimpleSwipeCards" target="_blank" rel="noopener">TinderSimpleSwipeCards</a></h4><p>可划动卡片，左划删除，右划选中</p><h4 id="4-CNPPopupController"><a href="#4-CNPPopupController" class="headerlink" title="4. CNPPopupController"></a>4. <a href="https://github.com/carsonperrotti/CNPPopupController" target="_blank" rel="noopener">CNPPopupController</a></h4><p>弹出式卡片<br><img src="https://raw.githubusercontent.com/carsonperrotti/CNPPopupController/master/CNPPopupControllerExample/CNPPopupController.gif" alt=""></p><h4 id="5-SwipeView"><a href="#5-SwipeView" class="headerlink" title="5. SwipeView"></a>5. <a href="https://github.com/nicklockwood/SwipeView" target="_blank" rel="noopener">SwipeView</a></h4><p>分页滑动视图</p><h3 id="日历"><a href="#日历" class="headerlink" title="日历"></a>日历</h3><h4 id="1-PDTSimpleCalendar"><a href="#1-PDTSimpleCalendar" class="headerlink" title="1. PDTSimpleCalendar"></a>1. <a href="https://github.com/jivesoftware/PDTSimpleCalendar" target="_blank" rel="noopener">PDTSimpleCalendar</a></h4><p>自定义日历</p><h3 id="下拉刷新"><a href="#下拉刷新" class="headerlink" title="下拉刷新"></a>下拉刷新</h3><h4 id="1-MJRefresh"><a href="#1-MJRefresh" class="headerlink" title="1. MJRefresh"></a>1. <a href="https://github.com/CoderMJLee/MJRefresh.git" target="_blank" rel="noopener">MJRefresh</a></h4><p>下拉刷新，上拉加载更多,国人写的，star数量很多</p><h4 id="2-CBStoreHouseRefreshControl"><a href="#2-CBStoreHouseRefreshControl" class="headerlink" title="2. CBStoreHouseRefreshControl"></a>2. <a href="https://github.com/coolbeet/CBStoreHouseRefreshControl" target="_blank" rel="noopener">CBStoreHouseRefreshControl</a></h4><p>效果酷炫，可以自己定制<br><img src="https://camo.githubusercontent.com/556662451b6de3d5c56a471ee5931ab8caf2c5e3/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f737579752e746573742f434253746f7265486f75736552656672657368436f6e74726f6c312e676966" alt=""></p><h3 id="UI"><a href="#UI" class="headerlink" title="UI"></a>UI</h3><h4 id="1-PPNumberButton"><a href="#1-PPNumberButton" class="headerlink" title="1. PPNumberButton"></a>1. <a href="https://github.com/jkpang/PPNumberButton" target="_blank" rel="noopener">PPNumberButton</a></h4><p>一款高度可定制性商品计数按钮</p><h4 id="2-FRDLivelyButton"><a href="#2-FRDLivelyButton" class="headerlink" title="2. FRDLivelyButton"></a>2. <a href="https://github.com/sebastienwindal/FRDLivelyButton" target="_blank" rel="noopener">FRDLivelyButton</a></h4><p>按钮动态变化效果UIButton</p><h4 id="3-DZNEmptyDataSet-🔥"><a href="#3-DZNEmptyDataSet-🔥" class="headerlink" title="3. DZNEmptyDataSet 🔥"></a>3. <a href="https://github.com/dzenbot/DZNEmptyDataSet" target="_blank" rel="noopener">DZNEmptyDataSet 🔥</a></h4><p>table展示空数据视图</p><h4 id="4-FXBlurView"><a href="#4-FXBlurView" class="headerlink" title="4. FXBlurView"></a>4. <a href="https://github.com/nicklockwood/FXBlurView" target="_blank" rel="noopener">FXBlurView</a></h4><p>对视图进行模糊操作</p><h4 id="5-ASValueTrackingSlider"><a href="#5-ASValueTrackingSlider" class="headerlink" title="5.ASValueTrackingSlider"></a>5.<a href="https://github.com/alskipp/ASValueTrackingSlider" target="_blank" rel="noopener">ASValueTrackingSlider</a></h4><p>UISlider带popoer跟随提示 star  1500+</p><h3 id="分段控件"><a href="#分段控件" class="headerlink" title="分段控件"></a>分段控件</h3><h4 id="1-HMSegmentedControl"><a href="#1-HMSegmentedControl" class="headerlink" title="1. HMSegmentedControl"></a>1. <a href="https://github.com/HeshamMegid/HMSegmentedControl" target="_blank" rel="noopener">HMSegmentedControl</a></h4><h4 id="2-RKSwipeBetweenViewControllers"><a href="#2-RKSwipeBetweenViewControllers" class="headerlink" title="2. RKSwipeBetweenViewControllers"></a>2. <a href="https://github.com/cwRichardKim/RKSwipeBetweenViewControllers" target="_blank" rel="noopener">RKSwipeBetweenViewControllers</a></h4><p>交互式分段控件,类似twitter首页划动效果</p><h4 id="3-BWTitlePagerView"><a href="#3-BWTitlePagerView" class="headerlink" title="3. BWTitlePagerView"></a>3. <a href="https://github.com/brunow/BWTitlePagerView" target="_blank" rel="noopener">BWTitlePagerView</a></h4><p>类似twitter分栏效果</p><h4 id="4-SMPageControl"><a href="#4-SMPageControl" class="headerlink" title="4. SMPageControl"></a>4. <a href="https://github.com/Spaceman-Labs/SMPageControl" target="_blank" rel="noopener">SMPageControl</a></h4><p>自定义pagecontrol。比较丰富</p><h3 id="AlertView-amp-ActionSheet"><a href="#AlertView-amp-ActionSheet" class="headerlink" title="AlertView &amp; ActionSheet"></a>AlertView &amp; ActionSheet</h3><h4 id="1-LMAlertView"><a href="#1-LMAlertView" class="headerlink" title="1. LMAlertView"></a>1. <a href="https://github.com/lmcd/LMAlertView" target="_blank" rel="noopener">LMAlertView</a></h4><p>自定义AlertView</p><h4 id="2-AHKActionSheet"><a href="#2-AHKActionSheet" class="headerlink" title="2. AHKActionSheet"></a>2. <a href="https://github.com/fastred/AHKActionSheet" target="_blank" rel="noopener">AHKActionSheet</a></h4><p>列表式的action sheet</p><h4 id="3-MMPopupView"><a href="#3-MMPopupView" class="headerlink" title="3. MMPopupView"></a>3. <a href="https://github.com/adad184/MMPopupView" target="_blank" rel="noopener">MMPopupView</a></h4><p>弹出框，alertview ，action sheet，date picker，验证码输入框</p><h3 id="角标"><a href="#角标" class="headerlink" title="角标"></a>角标</h3><h4 id="1-JSBadgeView"><a href="#1-JSBadgeView" class="headerlink" title="1. JSBadgeView"></a>1. <a href="https://github.com/JaviSoto/JSBadgeView" target="_blank" rel="noopener">JSBadgeView</a></h4><p>角标</p><h4 id="2-RKNotificationHub"><a href="#2-RKNotificationHub" class="headerlink" title="2. RKNotificationHub"></a>2. <a href="https://github.com/cwRichardKim/RKNotificationHub" target="_blank" rel="noopener">RKNotificationHub</a></h4><p>角标</p><h4 id="3-BBBadgeBarButtonItem"><a href="#3-BBBadgeBarButtonItem" class="headerlink" title="3. BBBadgeBarButtonItem"></a>3. <a href="https://github.com/TanguyAladenise/BBBadgeBarButtonItem" target="_blank" rel="noopener">BBBadgeBarButtonItem</a></h4><p>角标</p><h3 id="TableView"><a href="#TableView" class="headerlink" title="TableView"></a>TableView</h3><h4 id="1-RETableViewManager"><a href="#1-RETableViewManager" class="headerlink" title="1. RETableViewManager"></a>1. <a href="https://github.com/romaonthego/RETableViewManager" target="_blank" rel="noopener">RETableViewManager</a></h4><p>可以帮助你进行动态创建与管理table views。它给我们提供了预定义cells（bool类型、文本、日期等等——请看下面的截图），但是你还可以创建自定义views，并与默认视图一同使用。</p><h3 id="布局Layout"><a href="#布局Layout" class="headerlink" title="布局Layout"></a>布局Layout</h3><h4 id="1-MyLinearLayout"><a href="#1-MyLinearLayout" class="headerlink" title="1. MyLinearLayout"></a>1. <a href="https://github.com/youngsoft/MyLinearLayout" target="_blank" rel="noopener">MyLinearLayout</a></h4><p>UI布局</p><h4 id="2-CHTCollectionViewWaterfallLayout-🔥"><a href="#2-CHTCollectionViewWaterfallLayout-🔥" class="headerlink" title="2. CHTCollectionViewWaterfallLayout 🔥"></a>2. <a href="https://github.com/chiahsien/CHTCollectionViewWaterfallLayout" target="_blank" rel="noopener">CHTCollectionViewWaterfallLayout 🔥</a></h4><p>瀑布流layout</p><h4 id="3-LXReorderableCollectionViewFlowLayout"><a href="#3-LXReorderableCollectionViewFlowLayout" class="headerlink" title="3. LXReorderableCollectionViewFlowLayout"></a>3. <a href="https://github.com/lxcid/LXReorderableCollectionViewFlowLayout" target="_blank" rel="noopener">LXReorderableCollectionViewFlowLayout</a></h4><p>iOS 实现多种分类 添加删除排序功能 star 1600+</p><h3 id="轮播"><a href="#轮播" class="headerlink" title="轮播"></a>轮播</h3><h4 id="1-SDCycleScrollView"><a href="#1-SDCycleScrollView" class="headerlink" title="1. SDCycleScrollView"></a>1. <a href="https://github.com/gsdios/SDCycleScrollView" target="_blank" rel="noopener">SDCycleScrollView</a></h4><p>轮播</p><h3 id="文件"><a href="#文件" class="headerlink" title="文件"></a>文件</h3><h4 id="1-HYFileManager"><a href="#1-HYFileManager" class="headerlink" title="1. HYFileManager"></a>1. <a href="https://github.com/castial/HYFileManager" target="_blank" rel="noopener">HYFileManager</a></h4><p>简单实用的iOS文件工具类</p><h3 id="其它"><a href="#其它" class="headerlink" title="其它"></a>其它</h3><h4 id="1-SloppySwiper"><a href="#1-SloppySwiper" class="headerlink" title="1. SloppySwiper"></a>1. <a href="https://github.com/fastred/SloppySwiper" target="_blank" rel="noopener">SloppySwiper</a></h4><p>手势返回</p><h3 id="调试工具类"><a href="#调试工具类" class="headerlink" title="调试工具类"></a>调试工具类</h3><h4 id="1-FLEX"><a href="#1-FLEX" class="headerlink" title="1. FLEX"></a>1. <a href="https://github.com/Flipboard/FLEX" target="_blank" rel="noopener">FLEX</a></h4><p>是Flipboard开源的一系列在应用中调试的工具集</p><h4 id="2-KMCGeigerCounter"><a href="#2-KMCGeigerCounter" class="headerlink" title="2. KMCGeigerCounter"></a>2. <a href="https://github.com/kconner/KMCGeigerCounter" target="_blank" rel="noopener">KMCGeigerCounter</a></h4><p>动画帧速计算类库</p><h4 id="3-MMPlaceHolder"><a href="#3-MMPlaceHolder" class="headerlink" title="3. MMPlaceHolder"></a>3. <a href="https://github.com/adad184/MMPlaceHolder" target="_blank" rel="noopener">MMPlaceHolder</a></h4><p>app内显示UI尺寸</p><h4 id="4-MLeaksFinder"><a href="#4-MLeaksFinder" class="headerlink" title="4. MLeaksFinder"></a>4. <a href="https://github.com/Zepo/MLeaksFinder" target="_blank" rel="noopener">MLeaksFinder</a></h4><p>内存泄露检测，微信读书出品成员出品</p><h4 id="5-iRate-🔥"><a href="#5-iRate-🔥" class="headerlink" title="5. iRate 🔥"></a>5. <a href="https://github.com/nicklockwood/iRate" target="_blank" rel="noopener">iRate 🔥</a></h4><p>App Store中获得更多评论的最佳方式</p><h4 id="6-specta"><a href="#6-specta" class="headerlink" title="6. specta"></a>6. <a href="https://github.com/specta/specta" target="_blank" rel="noopener">specta</a></h4><p>自动测试</p><hr><h2 id="❋完整的项目开源代码❋"><a href="#❋完整的项目开源代码❋" class="headerlink" title="❋完整的项目开源代码❋"></a>❋完整的项目开源代码❋</h2><h4 id="1-Coding-iOS"><a href="#1-Coding-iOS" class="headerlink" title="1.Coding-iOS"></a>1.<a href="https://github.com/Coding/Coding-iOS" target="_blank" rel="noopener">Coding-iOS</a></h4><p> Coding_iOS客户端</p><hr><h2 id="❋Swift开源代码❋"><a href="#❋Swift开源代码❋" class="headerlink" title="❋Swift开源代码❋"></a>❋Swift开源代码❋</h2><h3 id="网络"><a href="#网络" class="headerlink" title="网络"></a>网络</h3><h4 id="1-Alamofire-🔥"><a href="#1-Alamofire-🔥" class="headerlink" title="1. Alamofire  🔥"></a>1. <a href="https://github.com/Alamofire/Alamofire" target="_blank" rel="noopener">Alamofire  🔥</a></h4><p>Alamofire是AFNetworking的小弟。更年轻更时尚|19000+</p><h3 id="颜色Color-amp-素材-1"><a href="#颜色Color-amp-素材-1" class="headerlink" title="颜色Color &amp; 素材"></a>颜色Color &amp; 素材</h3><h4 id="1-Chameleon-🔥-1"><a href="#1-Chameleon-🔥-1" class="headerlink" title="1. Chameleon  🔥"></a>1. <a href="https://github.com/ViccAlexander/Chameleon" target="_blank" rel="noopener">Chameleon  🔥</a></h4><p>一个iOS的色彩框架。它运用现代化flat color将UIColor扩展地非常美观。我们还可以通过它运用自定义颜色创建调色板(OC&amp;sWIFT)</p><h3 id="AlertView-amp-ActionSheet-1"><a href="#AlertView-amp-ActionSheet-1" class="headerlink" title="AlertView &amp; ActionSheet"></a>AlertView &amp; ActionSheet</h3><h4 id="1-PMAlertController"><a href="#1-PMAlertController" class="headerlink" title="1.PMAlertController"></a>1.<a href="https://github.com/Codeido/PMAlertController" target="_blank" rel="noopener">PMAlertController</a></h4><p>自定义alertview|1000+</p><h4 id="2-TBActionSheet"><a href="#2-TBActionSheet" class="headerlink" title="2.TBActionSheet"></a>2.<a href="https://github.com/yulingtianxia/TBActionSheet" target="_blank" rel="noopener">TBActionSheet</a></h4><p>自定义ActionSheet |430+</p><h3 id="自动布局-1"><a href="#自动布局-1" class="headerlink" title="自动布局"></a>自动布局</h3><h4 id="1-SnapKit-🔥"><a href="#1-SnapKit-🔥" class="headerlink" title="1. SnapKit 🔥"></a>1. <a href="https://github.com/SnapKit/SnapKit" target="_blank" rel="noopener">SnapKit 🔥</a></h4><p>swift自动布局 ☆7600+</p><h3 id="引导页-amp-启动页-1"><a href="#引导页-amp-启动页-1" class="headerlink" title="引导页 &amp; 启动页"></a>引导页 &amp; 启动页</h3><h4 id="1-Onboard-🔥-1"><a href="#1-Onboard-🔥-1" class="headerlink" title="1. Onboard 🔥"></a>1. <a href="https://github.com/mamaral/Onboard" target="_blank" rel="noopener">Onboard 🔥</a></h4><p>引导页(oc &amp; swift)<br><img src="https://github.com/mamaral/Onboard/raw/master/Screenshots/city.gif" alt=""><img src="https://github.com/mamaral/Onboard/raw/master/Screenshots/almanac.gif" alt=""></p><h3 id="动画-1"><a href="#动画-1" class="headerlink" title="动画"></a>动画</h3><h4 id="1-Spring-🔥"><a href="#1-Spring-🔥" class="headerlink" title="1. Spring  🔥"></a>1. <a href="https://github.com/MengTo/Spring" target="_blank" rel="noopener">Spring  🔥</a></h4><p>iOS动画 ☆8800+</p><h3 id="键盘-1"><a href="#键盘-1" class="headerlink" title="键盘"></a>键盘</h3><h4 id="1-IQKeyboardManager-1"><a href="#1-IQKeyboardManager-1" class="headerlink" title="1. IQKeyboardManager"></a>1. <a href="https://github.com/hackiftekhar/IQKeyboardManager.git" target="_blank" rel="noopener">IQKeyboardManager</a></h4><p>解决键盘在遮挡(OC &amp; sSwift) </p><h3 id="Json"><a href="#Json" class="headerlink" title="Json"></a>Json</h3><h4 id="1-SwiftyJSON"><a href="#1-SwiftyJSON" class="headerlink" title="1. SwiftyJSON"></a>1. <a href="https://github.com/SwiftyJSON/SwiftyJSON" target="_blank" rel="noopener">SwiftyJSON</a></h4><p>Swift下JSON解析 ☆11400+</p><h3 id="其它-1"><a href="#其它-1" class="headerlink" title="其它"></a>其它</h3><h4 id="1-PermissionScope"><a href="#1-PermissionScope" class="headerlink" title="1. PermissionScope"></a>1. <a href="https://github.com/nickoneill/PermissionScope" target="_blank" rel="noopener">PermissionScope</a></h4><p>是智能 iOS 用户权限 UI 和统一 API，可以智能的处理用户权限请求|2900+</p><h4 id="2-SwiftyDrop"><a href="#2-SwiftyDrop" class="headerlink" title="2. SwiftyDrop"></a>2. <a href="https://github.com/morizotter/SwiftyDrop" target="_blank" rel="noopener">SwiftyDrop</a></h4><p>轻量、易用的小清新弹出列表及信息提示组件|400</p><h4 id="3-KeychainAccess"><a href="#3-KeychainAccess" class="headerlink" title="3. KeychainAccess"></a>3. <a href="https://github.com/kishikawakatsumi/KeychainAccess" target="_blank" rel="noopener">KeychainAccess</a></h4><p>钥匙串管理|2000+</p><h3 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h3><h4 id="1-Quick"><a href="#1-Quick" class="headerlink" title="1.Quick"></a>1.<a href="https://github.com/Quick/Quick" target="_blank" rel="noopener">Quick</a></h4><p>swift自动测试|5200+</p><hr><p>其它资源：<a href="https://github.com/Tim9Liu9/TimLiu-iOS#视频播放" target="_blank" rel="noopener">iOS开发常用三方库、插件、知名博客等等</a></p>]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;❋Objective-C库❋&quot;&gt;&lt;a href=&quot;#❋Objective-C库❋&quot; class=&quot;headerlink&quot; title=&quot;❋Objective-C库❋&quot;&gt;&lt;/a&gt;❋Objective-C库❋&lt;/h2&gt;&lt;h3 id=&quot;网络请求&quot;&gt;&lt;a href=&quot;#网络请求&quot; class=&quot;headerlink&quot; title=&quot;网络请求&quot;&gt;&lt;/a&gt;网络请求&lt;/h3&gt;&lt;h4 id=&quot;1-AFNetworking-🔥&quot;&gt;&lt;a href=&quot;#1-AFNetworking-🔥&quot; class=&quot;headerlink&quot; title=&quot;1. AFNetworking  🔥&quot;&gt;&lt;/a&gt;1. &lt;a href=&quot;https://github.com/AFNetworking/AFNetworking.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AFNetworking  🔥&lt;/a&gt;&lt;/h4&gt;&lt;h3 id=&quot;浏览器&quot;&gt;&lt;a href=&quot;#浏览器&quot; class=&quot;headerlink&quot; title=&quot;浏览器&quot;&gt;&lt;/a&gt;浏览器&lt;/h3&gt;&lt;h4 id=&quot;1-SVWebViewController-🔥&quot;&gt;&lt;a href=&quot;#1-SVWebViewController-🔥&quot; class=&quot;headerlink&quot; title=&quot;1. SVWebViewController  🔥&quot;&gt;&lt;/a&gt;1. &lt;a href=&quot;https://github.com/TransitApp/SVWebViewController&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;SVWebViewController  🔥&lt;/a&gt;&lt;/h4&gt;
    
    </summary>
    
      <category term="开源库" scheme="https://flowyears.github.io/categories/%E5%BC%80%E6%BA%90%E5%BA%93/"/>
    
    
      <category term="iOS,开源库" scheme="https://flowyears.github.io/tags/iOS-%E5%BC%80%E6%BA%90%E5%BA%93/"/>
    
  </entry>
  
  <entry>
    <title>UITableView-Auto-Layout-(iOS 8+)</title>
    <link href="https://flowyears.github.io/2017/04/07/%5B%E8%AF%91%5DUITableView-Auto-Layout-(iOS-8+)/"/>
    <id>https://flowyears.github.io/2017/04/07/[译]UITableView-Auto-Layout-(iOS-8+)/</id>
    <published>2017-04-07T00:55:29.000Z</published>
    <updated>2017-04-09T13:21:18.000Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>翻译自stack overflow的一个<a href="http://stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights/18746930#18746930" target="_blank" rel="noopener">回答</a>，对于iOS自动布局很有用处。翻译水平有限，且现在公司的app都是iOS 8+，所以只翻译了iOS 8+的部分。如有错误，欢迎指正！谢谢！</p></blockquote><a id="more"></a><h2 id="概念描述"><a href="#概念描述" class="headerlink" title="概念描述"></a>概念描述</h2><p>无论你是哪个iOS开发版本，下面前2个步骤都适用。</p><h3 id="设置-amp-添加约束"><a href="#设置-amp-添加约束" class="headerlink" title="设置 &amp; 添加约束"></a>设置 &amp; 添加约束</h3><blockquote><p>content-hugging and compression-resistance 以下简称CHCR；<br>固有内容尺寸：intrinsic content size；</p></blockquote><p>在你的<code>UITableViewCell</code>子类中，添加约束使cell的subviews的边缘固定到cell的contentView的边缘（最重要的是顶部(top)和底部(bottom)边缘）。<strong>注意：不要把subviews固定到cell自身；只能添加到cell的 contentView！</strong>通过确保CHCR在每个subview的垂直方向不被你添加的更高优先级约束重写，来让这些subviews的固有内容尺寸驱动表单元的内容视图高度（<a href="http://stackoverflow.com/questions/22599069/what-is-the-content-compression-resistance-and-content-hugging-of-a-uiview" target="_blank" rel="noopener">嘿？点击这里</a>）。</p><p>Remember, the idea is to have the cell’s subviews connected vertically to the cell’s content view so that they can “exert pressure” and make the content view expand to fit them.使用一个例子，带有几个子视图的cell，这里有张直观的插图上面有一些（不是全部）你好像需要的约束：</p><p><img src="http://upload-images.jianshu.io/upload_images/760391-8e0ac86893b18698.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt=""></p><p>想象一下，随着更多文本被添加到在上面例子cell中的多行label，它将要垂直增长以适应文本，这将有效地强制cell增长高度（当然，为了正确地工作，你需要得到约束！）。</p><p>获取正确的约束绝对是自动布局获取动态cell高度最难且最重要的一部分。如果你在这里犯了错，它将阻止你的一切工作，所以抓紧时间！我建议在代码中设置你的约束，因为你确切知道哪些约束被添加到哪里，并且当事情出错时调试起来容易得多.在代码中添加约束和IB一样简单并且当你使用一个神奇的开放源码API的时候回更强大–这里就有一个我专门设计和维护和使用的：<a href="https://github.com/smileyborg/PureLayout" target="_blank" rel="noopener">https://github.com/smileyborg/PureLayout</a>.</p><ul><li>如果你在代码中添加约束，你应该在UITableViewCell 子类updateConstraints方法中执行一次添加。注意updateConstraints可能会不止调用一次，所以为了避免添加同样的约束多次，可以用一个bool值比如didSetupConstraints来检查包含的约束只添加一次（在约束添加之后设置为YES）。另一方面，如果你又代码要更新已存在的约束（比如调整constant属性相同的约束），将更新约束的代码发在updateConstraints中但是在didSetupConstraints的检查之外，这样更新的代码就可以在每次调用的时候执行了。</li></ul><h3 id="Determine-Unique-Table-View-Cell-Reuse-Identifiers"><a href="#Determine-Unique-Table-View-Cell-Reuse-Identifiers" class="headerlink" title="Determine Unique Table View Cell Reuse Identifiers"></a>Determine Unique Table View Cell Reuse Identifiers</h3><p>对于在cell中的每一组唯一约束，请使用唯一的cell reuse identifier。换句话说，如果你的单元格有多个唯一的布局，每个独特的布局应该得到它自己的重用标识符（当你的cell发生变化有不同数量的子视图或是以一种独特的方式布置子视图时，你需要使用一个新的reuse identifier）。</p><p>比如，例如，如果你在每个单元格中显示一个电子邮件消息，你可能有4个独特的布局：只有subject的消息；带subject和一个body的消息；带subject和照片附件的消息；带subject，body和照片附件的消息。每种布局都有完全不同的约束来实现它，所以一旦cell被初始化并且这些cell 类型之一添加了约束，cell就需要为指定的cell type获取一个唯一的reuse identifier。这意味着当你复用cell出列时，为这个cell type的约束已经被添加并且准备执行了。</p><p>注意，由于固有内容尺寸的差异，具有相同约束（类型）的单元格可能仍有不同的高度！不要因为不同的内容尺寸混淆根本不同的布局（不同的约束）和不同的算出来的视图frame。</p><ul><li>不要将具有完全不同约束集（sets of constraints）的cell添加到同一重用池（ reuse pool）中（比如使用相同的reuse identifier）并且在后面的队列中尝试删除旧约束从头开始添加新约束。内部自动布局引擎不是设计来处理大规模的约束变化，你会看到大量的性能问题。</li></ul><h3 id="Enable-Row-Height-Estimation"><a href="#Enable-Row-Height-Estimation" class="headerlink" title="Enable Row Height Estimation"></a>Enable Row Height Estimation</h3><p>在iOS 8中，苹果已经把许多在iOS 8之前需要由你完成的工作内部化实现了。为了让 self-sizing cell 机制工作，你必须首先设置tableview的<code>rowHeight</code>属性值为<code>UITableViewAutomaticDimension</code>.然后，你仅需要通过设置tableview的estimatedRowHeight属性值为一个非零值就可以开启行高估算（Row Height Estimation）。例如：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">self.tableView.rowHeight = UITableViewAutomaticDimension;</span><br><span class="line">self.tableView.estimatedRowHeight = 44.0; // set to whatever your &quot;average&quot; cell height is</span><br></pre></td></tr></table></figure><p>这个所做的是给tableview还未出现在屏幕上的cell的豪赌提供一个临时估算或占位。然后，当这些cell要滚动到屏幕上时实际的行高将会被计算。为了确定每行的实际高度，tableview自动询问每个cell它的contentview基于已知的content view固定宽度和你添加到cell的content view 及 subviews的自动布局约束所需要的高度。一旦cell的真实高度确定，估算高度就会更新为真实高度（任何tableview的contentSize/contentOffset调整需要你来做）。</p><p>一般来讲，你提供的估算并不需要非常准确–它只是用来合适的排列在tableview中的scroll indicator，在滚动屏幕上的单元格时，表视图可以很好地调整滚动指示器的估计值。你应该将表视图（在viewDidLoad或类似方法中）中的estimatedRowHeight属性设置为一个常量值，即“平均”行高。只有当你的行高有极大的可变性（例如相差一个数量级），你注意到滚动指示器“跳跃”，如果你麻烦执行<code>tableView：estimatedHeightForRowAtIndexPath：</code>做最小的计算，为每行返回更准确的估算值。</p><p><a href="https://github.com/smileyborg/TableViewCellWithAutoLayoutiOS8" target="_blank" rel="noopener">iOS的8示例项目</a> -需要iOS 8</p>]]></content>
    
    <summary type="html">
    
      &lt;blockquote&gt;
&lt;p&gt;翻译自stack overflow的一个&lt;a href=&quot;http://stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights/18746930#18746930&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;回答&lt;/a&gt;，对于iOS自动布局很有用处。翻译水平有限，且现在公司的app都是iOS 8+，所以只翻译了iOS 8+的部分。如有错误，欢迎指正！谢谢！&lt;/p&gt;
&lt;/blockquote&gt;
    
    </summary>
    
      <category term="自动布局" scheme="https://flowyears.github.io/categories/%E8%87%AA%E5%8A%A8%E5%B8%83%E5%B1%80/"/>
    
    
      <category term="iOS,autolayout" scheme="https://flowyears.github.io/tags/iOS-autolayout/"/>
    
  </entry>
  
  <entry>
    <title>《Auto-Layout-Guide》笔记</title>
    <link href="https://flowyears.github.io/2017/04/07/%E3%80%8AAuto-Layout-Guide%E3%80%8B%E7%AC%94%E8%AE%B0/"/>
    <id>https://flowyears.github.io/2017/04/07/《Auto-Layout-Guide》笔记/</id>
    <published>2017-04-07T00:55:29.000Z</published>
    <updated>2017-04-09T13:21:26.000Z</updated>
    
    <content type="html"><![CDATA[<h3 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h3><h4 id="约束优先级"><a href="#约束优先级" class="headerlink" title="约束优先级"></a>约束优先级</h4><p>所有约束都有1-1000的优先级。优先级为1000的约束是必须的。其它约束都是可选的。</p><a id="more"></a><blockquote><p>注意：不要强迫使用所有1000个优先级值。实际上，优先级通常聚集在系统定义的低（250），中（500），高（750）和必须（1000）周围。可能需要让约束的优先级比这些值高或低1-2点，来帮助阻止绑定（teis）。如果优先级远远超出这些值，可能需要重新检查布局逻辑。</p></blockquote><h4 id="固有内容尺寸-Intrinsic-Content-Size"><a href="#固有内容尺寸-Intrinsic-Content-Size" class="headerlink" title="固有内容尺寸(Intrinsic Content Size)"></a>固有内容尺寸(Intrinsic Content Size)</h4><p>有些视图根据当前给定的内容有个自然的尺寸。这个尺寸被称为<code>固有内容尺寸(Intrinsic Content Size)</code>。</p><p>不是所有视图都有固有内容尺寸。对于有固有内容尺寸的视图，<strong>它可以定义视图的高度，宽度或者两者都定义</strong>。下面的表格列出了一些例子（这里只标注iOS的，mac OS已删除）。</p><table><thead><tr><th>视图</th><th>固有内容尺寸</th></tr></thead><tbody><tr><td>UIView</td><td>没有固有内容尺寸</td></tr><tr><td>UISlider</td><td>只定义了宽度。</td></tr><tr><td>UILabel,UIbutton,UISwitch,UITextField</td><td>同时定义了宽度和高度。</td></tr><tr><td>UITextView,UIImageView</td><td>固有内容尺寸可以改变。</td></tr></tbody></table><h4 id="Content-hugging-amp-Compression-resistance"><a href="#Content-hugging-amp-Compression-resistance" class="headerlink" title="Content hugging &amp; Compression resistance"></a>Content hugging &amp; Compression resistance</h4><p>自动布局使用了一对约束来呈现一个视图的每个维度。content hugging向内抱紧视图，使紧贴内容（简称抱紧我）。压缩阻力（compression resistance）向外推视图，使其不剪辑内容（简称别挤我）。</p><p>Content hugging优先级越高，抱的越紧；compression resistance优先级越高，压缩阻力越大，内容越不容易被剪辑。</p><p>下面举两个例子说明一下：</p><ul><li>例1（Content hugging）<br>通常会有这样一个功能，左侧label，提示输入名字，右侧是一个textfield，用于输入名字，如下图所示：</li></ul><p><img src="http://upload-images.jianshu.io/upload_images/760391-3843819524c2a48a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt=""><br>这时候我们希望name足够小，能显示name足以，textfield则尽量拉伸。<br>这时候只需要name的Content hugging属性优先级大于textfield就可以了。</p><ul><li>例2（compression resistance）<br>给一个label添加宽度为80的约束，如果label的文字超过80，那么显示就会这样：</li></ul><p><img src="http://upload-images.jianshu.io/upload_images/760391-e2ef14402782078f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt=""></p><p>因为compression resistance默认优先级为750，而高宽这些数据的优先级默认是required（即1000）。所以我们只要将宽度的约束优先级修改一下，比750小即可，比如749.这样修改后，效果如下：</p><p><img src="http://upload-images.jianshu.io/upload_images/760391-2cde8b491ccabb84.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt=""></p><p>这样应该就能很好理解两个属性的意义了。</p><p>常见关系优先级：</p><table><thead><tr><th>属性或关系</th><th>优先级</th></tr></thead><tbody><tr><td>equal</td><td>1000</td></tr><tr><td>greater-than-or-equal</td><td>1000</td></tr><tr><td> less-than-or-equal</td><td>1000</td></tr><tr><td>Content hugging</td><td>250</td></tr><tr><td>Compression resistance</td><td>750</td></tr></tbody></table><h3 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h3><h4 id="Align工具"><a href="#Align工具" class="headerlink" title="Align工具"></a>Align工具</h4><p>Align工具可以快速对齐布局中的项。选择想要<strong>对齐</strong>的项，然后单击Align工具。界面生成器显示一个弹出框视图，其中包括一些可能的对齐选项。</p><p><img src="http://upload-images.jianshu.io/upload_images/760391-2a0c61da751892b8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt=""></p><h4 id="Pin工具"><a href="#Pin工具" class="headerlink" title="Pin工具"></a>Pin工具</h4><p>Pin工具可以快速定义视图相对于它<strong>邻居</strong>的位置，或者快速定义视图的尺寸。选择想要固定(pin)位置或尺寸的项，然后点击Pin工具。界面生成器显示一个弹出框视图，其中包括一些选项。</p><p><img src="http://upload-images.jianshu.io/upload_images/760391-3c95091daf09a074.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt=""></p><p>弹出框顶部区域可以固定选中项的开头，顶部，结尾或底部边缘到它最近的邻居。关联的数字表示画布中两个项之间的当前间隔。可以输入自定义的间隔，或者点击三角形，设置它被约束到哪个视图，或者选择标准间隔。Constrain to margins复选框决定约束使用父视图的页边留白（margin）还是它的边缘（edge）。</p><p><img src="http://upload-images.jianshu.io/upload_images/760391-753104f642a87016.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt=""></p><p>Resolve Auto Layout Issues工具<br>Resolve Auto Layout Issues工具提供了一些选项用来修复常见的自动布局问题。菜单的上半部分只影响当前选中的视图。下半部分选项影响场景中所有视图。</p><p><img src="http://upload-images.jianshu.io/upload_images/760391-7c68e65d852366cb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt=""><br>可以使用这个工具更新视图的框架（frame），基于当前的约束，或者根据视图在画布中的当前位置更新约束。还可以添加缺失的约束，清理约束，或者重置视图为界面生成器推荐的约束。</p><h3 id="调试技巧"><a href="#调试技巧" class="headerlink" title="调试技巧"></a>调试技巧</h3><p>在自动布局过程中，出现错误是经常的事情，自动布局中的错误主要分为三个类型：</p><ul><li><strong>不可满足的（unsatisfiable）布局。</strong>布局没有有效的解。更多信息请参考<a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/ConflictingLayouts.html#//apple_ref/doc/uid/TP40010853-CH19-SW1" target="_blank" rel="noopener">不可满足的布局</a>。</li><li><strong>有歧义的布局。</strong>你的布局有两个或多个可能的解。更多信息请参考<a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/AmbiguousLayouts.html#//apple_ref/doc/uid/TP40010853-CH18-SW1" target="_blank" rel="noopener">有歧义的布局</a>。</li><li><strong>逻辑错误。</strong>布局逻辑中存在bug。更多信息请参考<a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/LogicalErrors.html#//apple_ref/doc/uid/TP40010853-CH20-SW1" target="_blank" rel="noopener">逻辑错误</a>。</li></ul><p>调试方法：<br>查看控制台的打印信息，有时候打印的信息比较多，不方便看，添加标识符，是一个很好的方法。</p><h3 id="高级自动布局"><a href="#高级自动布局" class="headerlink" title="高级自动布局"></a>高级自动布局</h3><h4 id="推迟的布局过程"><a href="#推迟的布局过程" class="headerlink" title="推迟的布局过程"></a>推迟的布局过程</h4><p>自动布局安排一个布局过程在不久的将来取代直接更新受影响的view的frame。该推迟的过程更新布局的约束，然后计算视图层次结构中所有视图的frame。</p><p>可以通过调用<a href="https://developer.apple.com/reference/uikit/uiview/1622601-setneedslayout" target="_blank" rel="noopener">setNeedsLayout</a>方法或者<a href="https://developer.apple.com/reference/uikit/uiview/1622450-setneedsupdateconstraints" target="_blank" rel="noopener">setNeedsUpdateConstraints</a>方法，调度自己的推迟的布局过程。</p><p>推迟的布局过程实际涉及视图层级结构的两个过程：<br>1.更新过程根据需要更新约束。<br>2.布局过程根据需要重新定位视图的frame。</p><ol><li><p>更新过程<br>系统遍历视图层级结构，并在所有视图控制器上调用<a href="https://developer.apple.com/reference/uikit/uiviewcontroller/1621379-updateviewconstraints" target="_blank" rel="noopener">updateViewConstraints</a>方法，在所有视图上调用<a href="https://developer.apple.com/reference/uikit/uiview/1622512-updateconstraints" target="_blank" rel="noopener">updateConstraints</a>方法。你可以覆写这些方法，来优化约束的改变（查看<a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/ModifyingConstraints.html#//apple_ref/doc/uid/TP40010853-CH29-SW2" target="_blank" rel="noopener">批量改变</a>）。</p></li><li><p>布局过程<br>系统再次遍历视图层级结构，并在所有视图控制器上调用<a href="https://developer.apple.com/reference/uikit/uiviewcontroller/1621437-viewwilllayoutsubviews" target="_blank" rel="noopener">viewWillLayoutSubviews</a>方法，在所有视图上调用<a href="https://developer.apple.com/reference/uikit/uiview/1622482-layoutsubviews" target="_blank" rel="noopener">layoutSubviews</a>。默认情况下，layoutSubviews方法使用自动布局引擎计算的矩形更新每个子视图的frame。你可以覆写这些方法来修改布局（查看<a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/ModifyingConstraints.html#//apple_ref/doc/uid/TP40010853-CH29-SW4" target="_blank" rel="noopener">自定义布局</a>）。</p></li></ol><h4 id="批量改变"><a href="#批量改变" class="headerlink" title="批量改变"></a>批量改变</h4><p>影响变化发生后，立即更新约束几乎总是更干净和容易。推迟这些改变到一个之后的方法会让代码复杂，更难理解。</p><p>然而，有些时候你可能基于性能原因，希望批量修改。只有在就地改变约束太慢，或者当视图做了很多多余的改变时，才应该这么做。</p><p>要想批量改变，在持有约束的视图上调用<a href="https://developer.apple.com/reference/uikit/uiview/1622450-setneedsupdateconstraints" target="_blank" rel="noopener">setNeedsUpdateConstraints</a>方法，而不是直接做出改变。然后，覆写视图的<a href="https://developer.apple.com/reference/uikit/uiview/1622512-updateconstraints" target="_blank" rel="noopener">updateConstraints</a>方法，来修改受影响的约束。</p><blockquote><p><strong>提示</strong>:你的updateConstraints实现必须尽可能高效。不要禁用所有约束，然后启用你需要的。相反，你的应用程序必须有些方式来追踪你的约束，并在每个更新过程中验证它们。只有变化的项需要改变。在每一个更新过程中，你必须确保应用程序的当前状态有合适的约束。</p></blockquote><p><strong>总是在你实现的updateConstraints方法的最后一步调用父类的实现</strong>。</p><p>不要在你的updateConstraints方法中调用setNeedsUpdateConstraints。调用setNeedsUpdateConstraints调度另一个更新过程，创建了一个反馈回路（feedback loop）。</p><p>参考：<br><a href="https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/ModifyingConstraints.html#//apple_ref/doc/uid/TP40010853-CH29-SW1" target="_blank" rel="noopener">Auto Layout Guide</a><br><a href="http://www.jianshu.com/p/5d5562106756" target="_blank" rel="noopener">自动布局指南</a></p>]]></content>
    
    <summary type="html">
    
      &lt;h3 id=&quot;概念&quot;&gt;&lt;a href=&quot;#概念&quot; class=&quot;headerlink&quot; title=&quot;概念&quot;&gt;&lt;/a&gt;概念&lt;/h3&gt;&lt;h4 id=&quot;约束优先级&quot;&gt;&lt;a href=&quot;#约束优先级&quot; class=&quot;headerlink&quot; title=&quot;约束优先级&quot;&gt;&lt;/a&gt;约束优先级&lt;/h4&gt;&lt;p&gt;所有约束都有1-1000的优先级。优先级为1000的约束是必须的。其它约束都是可选的。&lt;/p&gt;
    
    </summary>
    
      <category term="自动布局" scheme="https://flowyears.github.io/categories/%E8%87%AA%E5%8A%A8%E5%B8%83%E5%B1%80/"/>
    
    
  </entry>
  
  <entry>
    <title>详解自动布局(Masonry)实现九宫格</title>
    <link href="https://flowyears.github.io/2017/04/07/%E8%AF%A6%E8%A7%A3%E8%87%AA%E5%8A%A8%E5%B8%83%E5%B1%80%EF%BC%88Masonry%EF%BC%89%E5%AE%9E%E7%8E%B0%E4%B9%9D%E5%AE%AB%E6%A0%BC/"/>
    <id>https://flowyears.github.io/2017/04/07/详解自动布局（Masonry）实现九宫格/</id>
    <published>2017-04-07T00:55:29.000Z</published>
    <updated>2017-04-09T13:27:26.000Z</updated>
    
    <content type="html"><![CDATA[<p>以前写TimeLine中照片九宫格布局是直接计算frame，今天想用自动布局实现。<br><a id="more"></a></p><h3 id="九宫格布局"><a href="#九宫格布局" class="headerlink" title="九宫格布局"></a>九宫格布局</h3><p>使用自动布局，首先就必须知道给出了哪些条件。一般在TimeLine中照片九宫格布局给出的已知条件为：</p><ol><li>每个单元的宽cellWidth；</li><li>每个单元的高cellHeight；</li><li>每行有几个单元numPerRow；</li><li>总共单元个数totalNum；</li><li>每个单元与边界间距viewPadding；</li><li>每个单元之间的间距viewPaddingCell。</li></ol><p><img src="http://upload-images.jianshu.io/upload_images/760391-f9e752bfc5b8ffb5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="图 1"></p><p>图 1是一个九宫格，黄色区域为父视图，由已知条件可知，它的大小是由里面的单元格布局决定的，所以，只要固定住里面的单元格，父视图就会自动固定住。<br>下面我们来添加约束：<br>1.所有单元格添加高度（height）和宽度（width）约束，如图 2所示，</p><p><img src="http://upload-images.jianshu.io/upload_images/760391-e6bb6f6d5a20d779.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="图 2"></p><p>2.第一行相对父视图，添加top约束，如图三中紫色箭头所示，</p><p><img src="http://upload-images.jianshu.io/upload_images/760391-8e6d4276d6da399d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="图 3"><br>3.非第一行添加对上一行单元的top约束，如图四红色箭头所示，</p><p><img src="http://upload-images.jianshu.io/upload_images/760391-87120ed7d6ea322c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="图 4"></p><p>4.第一列添加对父视图的left约束，如图五墨绿色箭头所示</p><p><img src="http://upload-images.jianshu.io/upload_images/760391-a6130621009191d4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="图 5"></p><p>5.非第一列添加对上一个view的left约束，如图6深蓝色箭头所示</p><p><img src="http://upload-images.jianshu.io/upload_images/760391-3fd770e36d6a6966.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="图 6"></p><p>这时候你会发现所有单元格都固定了，但是父视图的大小却不能固定，因为还不能得出父视图的宽和高</p><p>6.右上角（第一行&amp;最后一列）添加对父视图right约束，如图7所示，2号单元格右侧绿色箭头</p><p><img src="http://upload-images.jianshu.io/upload_images/760391-e608894c34ba15d8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="图 7"></p><p>7.左下角（最后一行&amp;第一列）添加对父视图的bottom约束，如图8所示，6号单元格底部紫色箭头</p><p><img src="http://upload-images.jianshu.io/upload_images/760391-b4360a56a4693dbd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="图 8"></p><p>talk is cheap show me the code.</p><p><img src="http://upload-images.jianshu.io/upload_images/760391-4e01cb9f1c2a49e5.gif?imageMogr2/auto-orient/strip" alt="doge.gif"></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">/**</span><br><span class="line"> 九宫格布局（不限于九宫格，可以是N个格子），每个格子给定高（cellHeight）宽（cellWidth），</span><br><span class="line"> 每行格子数量（numPerRow），格子总数量（totalNum），格子与边界距离（viewPadding），格</span><br><span class="line"> 子之间的距离（viewPaddingCell）。</span><br><span class="line"></span><br><span class="line"> @param cellWidth       格子宽度</span><br><span class="line"> @param cellHeight      格子高度</span><br><span class="line"> @param numPerRow       每行格子数量</span><br><span class="line"> @param totalNum        格子总数量</span><br><span class="line"> @param viewPadding     格子与边界距离</span><br><span class="line"> @param viewPaddingCell 格子之间的距离</span><br><span class="line"> @param superView       父视图</span><br><span class="line"> */</span><br><span class="line">- (void)gridWithCellWidth:(CGFloat)cellWidth</span><br><span class="line">               cellHeight:(CGFloat)cellHeight</span><br><span class="line">                numPerRow:(NSInteger)numPerRow</span><br><span class="line">                 totalNum:(NSInteger)totalNum</span><br><span class="line">              viewPadding:(CGFloat)viewPadding</span><br><span class="line">          viewPaddingCell:(CGFloat)viewPaddingCell</span><br><span class="line">                superView:(UIView *)superView</span><br><span class="line"></span><br><span class="line">&#123;</span><br><span class="line">    </span><br><span class="line">    __block UILabel *lastView = nil;// 创建一个空view 代表上一个view</span><br><span class="line">    __block UILabel *lastRowView;// 创建一个空view 代表上一行view</span><br><span class="line">    </span><br><span class="line">    __block NSInteger lastRowNo = 0;//上一行的行号</span><br><span class="line">    NSMutableArray *cells = [[NSMutableArray alloc] init];</span><br><span class="line">    </span><br><span class="line">    </span><br><span class="line">    for (int i = 0; i &lt; totalNum; i++) &#123;</span><br><span class="line">        </span><br><span class="line">        UILabel *aLabel = [UILabel new];</span><br><span class="line">        aLabel.text = [NSString stringWithFormat:@&quot;%d&quot;,i];</span><br><span class="line">        [superView addSubview:aLabel];</span><br><span class="line">        aLabel.backgroundColor = [UIColor colorWithHue:(arc4random() % 256 / 256.0 ) saturation:( arc4random() % 128 / 256.0 ) + 0.5</span><br><span class="line">                                          brightness:( arc4random() % 128 / 256.0 ) + 0.5 alpha:1.0];</span><br><span class="line">        [cells addObject:aLabel];</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    // 循环创建view</span><br><span class="line">    for (int i = 0; i &lt; cells.count; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        </span><br><span class="line">        UILabel *lb = cells[i];</span><br><span class="line"></span><br><span class="line">        </span><br><span class="line">        BOOL isFirstRow = [self isFirstRowWithIndex:i numOfRow:numPerRow];</span><br><span class="line">        BOOL isFirstCol = [self isFirstColumnWithIndex:i numOfRow:numPerRow];</span><br><span class="line">        </span><br><span class="line">        BOOL isLastCol = [self isLastColumnWithIndex:i numOfRow:numPerRow totalNum:totalNum];</span><br><span class="line">        BOOL isLastRow = [self isLastRowWithIndex:i numOfRow:numPerRow totalNum:totalNum];</span><br><span class="line">        </span><br><span class="line">        NSInteger curRowNo = i/numPerRow;</span><br><span class="line">        if (curRowNo != lastRowNo)</span><br><span class="line">        &#123;//如果当前行与上一个view行不等，说明换行了</span><br><span class="line">            lastRowView = lastView;</span><br><span class="line">            lastRowNo = curRowNo;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        // 添加约束</span><br><span class="line">        [lb mas_makeConstraints:^(MASConstraintMaker *make) &#123;</span><br><span class="line">            make.width.equalTo(@(cellWidth));</span><br><span class="line">            make.height.equalTo(@(cellHeight));</span><br><span class="line">            </span><br><span class="line">            if (isFirstRow)</span><br><span class="line">            &#123;</span><br><span class="line">                make.top.equalTo(superView.mas_top).with.offset(viewPadding);</span><br><span class="line">            &#125;</span><br><span class="line">            else</span><br><span class="line">            &#123;</span><br><span class="line">                if (lastRowView)</span><br><span class="line">                &#123;</span><br><span class="line">                    make.top.equalTo(lastRowView.mas_bottom).with.offset(viewPaddingCell);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            if (isFirstCol)</span><br><span class="line">            &#123;</span><br><span class="line">                make.left.equalTo(superView.mas_left).with.offset(viewPadding);</span><br><span class="line">            &#125;</span><br><span class="line">            else</span><br><span class="line">            &#123;</span><br><span class="line">                if (lastView)</span><br><span class="line">                &#123;</span><br><span class="line">                    make.left.equalTo(lastView.mas_right).with.offset(viewPaddingCell);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            if (isFirstRow &amp;&amp; isLastCol)</span><br><span class="line">            &#123;</span><br><span class="line">                make.right.equalTo(superView.mas_right).with.offset(-viewPadding);</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">            if (isLastRow &amp;&amp; isFirstCol)</span><br><span class="line">            &#123;</span><br><span class="line">                make.bottom.equalTo(superView.mas_bottom).with.offset(-viewPadding);</span><br><span class="line">            &#125;</span><br><span class="line">            </span><br><span class="line">        &#125;];</span><br><span class="line">        </span><br><span class="line">        </span><br><span class="line">        </span><br><span class="line">        // 每次循环结束 此次的View为下次约束的基准</span><br><span class="line">        lastView = lb;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>代码中有一些判断，比如是否为第一行，</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> 是否第一行</span><br><span class="line"></span><br><span class="line"> @param index    当前下标</span><br><span class="line"> @param numOfRow 每行个数</span><br><span class="line"></span><br><span class="line"> @return YES OR NO</span><br><span class="line"> */</span><br><span class="line">- (BOOL)isFirstRowWithIndex:(NSInteger)index numOfRow:(NSInteger)numOfRow</span><br><span class="line">&#123;</span><br><span class="line">    if (numOfRow != 0)</span><br><span class="line">    &#123;</span><br><span class="line">        return index/numOfRow == 0;</span><br><span class="line">    &#125;</span><br><span class="line">    return NO;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>是否为第一列，<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> 是否第一列</span><br><span class="line"> </span><br><span class="line"> @param index    当前下标</span><br><span class="line"> @param numOfRow 每行个数</span><br><span class="line"> </span><br><span class="line"> @return YES OR NO</span><br><span class="line"> */</span><br><span class="line">- (BOOL)isFirstColumnWithIndex:(NSInteger)index numOfRow:(NSInteger)numOfRow</span><br><span class="line">&#123;</span><br><span class="line">    if (numOfRow != 0)</span><br><span class="line">    &#123;</span><br><span class="line">        return index%numOfRow == 0;</span><br><span class="line">    &#125;</span><br><span class="line">    return NO;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>是否为最后一行，<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> 是否最后一行</span><br><span class="line"> </span><br><span class="line"> @param index    当前下标</span><br><span class="line"> @param numOfRow 每行个数</span><br><span class="line"> </span><br><span class="line"> @return YES OR NO</span><br><span class="line"> */</span><br><span class="line">- (BOOL)isLastRowWithIndex:(NSInteger)index numOfRow:(NSInteger)numOfRow totalNum:(NSInteger)totalNum</span><br><span class="line">&#123;</span><br><span class="line">    NSInteger totalRow = ceil(totalNum/((CGFloat)numOfRow));//总行数</span><br><span class="line">    </span><br><span class="line">    if (numOfRow != 0)</span><br><span class="line">    &#123;</span><br><span class="line">        return index/numOfRow == totalRow - 1;</span><br><span class="line">    &#125;</span><br><span class="line">    return NO;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p><p>是否为最后一列</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> 是否最后一列</span><br><span class="line"> </span><br><span class="line"> @param index    当前下标</span><br><span class="line"> @param numOfRow 每行个数</span><br><span class="line"> </span><br><span class="line"> @return YES OR NO</span><br><span class="line"> */</span><br><span class="line">- (BOOL)isLastColumnWithIndex:(NSInteger)index numOfRow:(NSInteger)numOfRow totalNum:(NSInteger)totalNum</span><br><span class="line">&#123;</span><br><span class="line">    if (numOfRow != 0)</span><br><span class="line">    &#123;</span><br><span class="line">        if (totalNum &lt; numOfRow)</span><br><span class="line">        &#123;//总数小于每行最大个数时，如果index是最后一个，那么也是最后一列</span><br><span class="line">            return index == totalNum-1;</span><br><span class="line">        &#125;</span><br><span class="line">        return index%numOfRow == numOfRow - 1;</span><br><span class="line">    &#125;</span><br><span class="line">    return NO;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>注意</strong>：这里有个地方要注意，当你的单元格总数（totalNum ）小于每行个数 （numOfRow），比如总共有2个单元格，每行排三个，那么最后一个即为最后一列。</p><p>上面这四个判断在很多地方都可以用到，可以记下备用🙂。</p><p>然后是上一行的view判断也需要注意。</p><p>其实这不单单只是九宫格布局，N个单元格布局也是可以的，感兴趣的小伙胖可以自行测试（好吧，估计你从我写的方法名已经看出来了，每行个数和总个数我都没有写死😂）。</p><h3 id="另一种九宫格"><a href="#另一种九宫格" class="headerlink" title="另一种九宫格"></a>另一种九宫格</h3><p>这里的九宫格布局是子视图固定，而父视图由子视图决定，还有另一种情况：父视图高宽固定，子视图与父视图边界距离给定，子视图间距给定。<br>知道怎么布局吗？可以先思考一下。<br>|<br>|<br>|<br>|<br>|<br>|<br>好吧，揭晓答案：只要按照第一种九宫格前5个步骤来添加约束即可，去掉最后两步。</p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>一开始我只是想布局一个九宫格，但是后来又想，如果需求扩展到了N个单元，该如何实现呢，我的办法是从九宫格开始，由小及大来推导，然后就是要知道自动布局需要添加哪些约束，能够完整的固定视图，不能多，也不要少，这是很重要的。</p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;以前写TimeLine中照片九宫格布局是直接计算frame，今天想用自动布局实现。&lt;br&gt;
    
    </summary>
    
      <category term="自动布局" scheme="https://flowyears.github.io/categories/%E8%87%AA%E5%8A%A8%E5%B8%83%E5%B1%80/"/>
    
    
  </entry>
  
  <entry>
    <title>Hello World</title>
    <link href="https://flowyears.github.io/2017/04/06/hello-world/"/>
    <id>https://flowyears.github.io/2017/04/06/hello-world/</id>
    <published>2017-04-06T00:55:29.000Z</published>
    <updated>2017-04-09T13:21:38.000Z</updated>
    
    <content type="html"><![CDATA[<p>Welcome to <a href="https://hexo.io/" target="_blank" rel="noopener">Hexo</a>! This is your very first post. Check <a href="https://hexo.io/docs/" target="_blank" rel="noopener">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="https://hexo.io/docs/troubleshooting.html" target="_blank" rel="noopener">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues" target="_blank" rel="noopener">GitHub</a>.<br><a id="more"></a></p><h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo new <span class="string">"My New Post"</span></span><br></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/writing.html" target="_blank" rel="noopener">Writing</a></p><h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo server</span><br></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/server.html" target="_blank" rel="noopener">Server</a></p><h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo generate</span><br></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/generating.html" target="_blank" rel="noopener">Generating</a></p><h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo deploy</span><br></pre></td></tr></table></figure><p>More info: <a href="https://hexo.io/docs/deployment.html" target="_blank" rel="noopener">Deployment</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Welcome to &lt;a href=&quot;https://hexo.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Hexo&lt;/a&gt;! This is your very first post. Check &lt;a href=&quot;https://hexo.io/docs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;documentation&lt;/a&gt; for more info. If you get any problems when using Hexo, you can find the answer in &lt;a href=&quot;https://hexo.io/docs/troubleshooting.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;troubleshooting&lt;/a&gt; or you can ask me on &lt;a href=&quot;https://github.com/hexojs/hexo/issues&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;GitHub&lt;/a&gt;.&lt;br&gt;
    
    </summary>
    
    
  </entry>
  
</feed>
